Merge "Add TEST_MAPPINGs for CDM"
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 2bd5aee..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: [
@@ -311,6 +312,15 @@
     compile_dex: true,
 }
 
+java_defaults {
+    name: "android_stubs_dists_default",
+    dist: {
+        targets: ["sdk", "win_sdk"],
+        tag: ".jar",
+        dest: "android.jar",
+    },
+}
+
 java_library_static {
     name: "android_monolith_stubs_current",
     srcs: [ ":api-stubs-docs" ],
@@ -346,7 +356,21 @@
     name: "android_system_monolith_stubs_current",
     srcs: [ ":system-api-stubs-docs" ],
     static_libs: [ "private-stub-annotations-jar" ],
-    defaults: ["android_defaults_stubs_current"],
+    defaults: [
+        "android_defaults_stubs_current",
+        "android_stubs_dists_default",
+    ],
+    dist: {
+        dir: "apistubs/android/system",
+    },
+    dists: [
+        {
+            // Legacy dist path
+            targets: ["sdk", "win_sdk"],
+            tag: ".jar",
+            dest: "android_system.jar",
+        },
+    ],
 }
 
 java_library_static {
@@ -378,14 +402,34 @@
     name: "android_test_stubs_current",
     srcs: [ ":test-api-stubs-docs" ],
     static_libs: [ "private-stub-annotations-jar" ],
-    defaults: ["android_defaults_stubs_current"],
+    defaults: [
+        "android_defaults_stubs_current",
+        "android_stubs_dists_default",
+    ],
+    dist: {
+        dir: "apistubs/android/test",
+    },
+    dists: [
+        {
+            // Legacy dist path
+            targets: ["sdk", "win_sdk"],
+            tag: ".jar",
+            dest: "android_test.jar",
+        },
+    ],
 }
 
 java_library_static {
     name: "android_module_lib_stubs_current",
     srcs: [ ":module-lib-api-stubs-docs-non-updatable" ],
-    defaults: ["android_defaults_stubs_current"],
+    defaults: [
+        "android_defaults_stubs_current",
+        "android_stubs_dists_default",
+    ],
     libs: ["sdk_system_29_android"],
+    dist: {
+        dir: "apistubs/android/module-lib",
+    },
 }
 
 java_library_static {
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
index 050fecd..d3938f4 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.view.Display;
@@ -136,4 +135,22 @@
             }
         }
     }
+
+    @Test
+    public void getDisplayMetrics() {
+        ResourcesManager resourcesManager = ResourcesManager.getInstance();
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            // Invalidate cache.
+            resourcesManager.applyConfigurationToResourcesLocked(
+                    resourcesManager.getConfiguration(), null);
+            state.resumeTiming();
+
+            // Invoke twice for testing cache.
+            resourcesManager.getDisplayMetrics();
+            resourcesManager.getDisplayMetrics();
+        }
+    }
 }
diff --git a/apct-tests/perftests/multiuser/AndroidTest.xml b/apct-tests/perftests/multiuser/AndroidTest.xml
index c7929af..fbe5892 100644
--- a/apct-tests/perftests/multiuser/AndroidTest.xml
+++ b/apct-tests/perftests/multiuser/AndroidTest.xml
@@ -27,6 +27,9 @@
     <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 -->
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
index 93bf541..e192861 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
@@ -53,7 +53,8 @@
     private static final int NOT_STARTED = 0;  // The benchmark has not started yet.
     private static final int WARMUP = 1; // The benchmark is warming up.
     private static final int RUNNING = 2;  // The benchmark is running.
-    private static final int FINISHED = 3;  // The benchmark has stopped.
+    private static final int RUNNING_CUSTOMIZED = 3;  // Running for customized measurement.
+    private static final int FINISHED = 4;  // The benchmark has stopped.
 
     private int mState = NOT_STARTED;  // Current benchmark state.
 
@@ -76,6 +77,14 @@
 
     private int mRepeatCount = 0;
 
+    /**
+     * Additional iteration that used to apply customized measurement. The result during these
+     * iterations won't be counted into {@link #mStats}.
+     */
+    private int mMaxCustomizedIterations;
+    private int mCustomizedIterations;
+    private CustomizedIterationListener mCustomizedIterationListener;
+
     // Statistics. These values will be filled when the benchmark has finished.
     // The computation needs double precision, but long int is fine for final reporting.
     private Stats mStats;
@@ -110,6 +119,15 @@
         mPaused = false;
     }
 
+    /**
+     * This is used to run the benchmark with more information by enabling some debug mechanism but
+     * we don't want to account the special runs (slower) in the stats report.
+     */
+    public void setCustomizedIterations(int iterations, CustomizedIterationListener listener) {
+        mMaxCustomizedIterations = iterations;
+        mCustomizedIterationListener = listener;
+    }
+
     private void beginWarmup() {
         mStartTimeNs = System.nanoTime();
         mIteration = 0;
@@ -141,6 +159,11 @@
                 Debug.stopMethodTracing();
             }
             mStats = new Stats(mResults);
+            if (mMaxCustomizedIterations > 0 && mCustomizedIterationListener != null) {
+                mState = RUNNING_CUSTOMIZED;
+                mCustomizedIterationListener.onStart(mCustomizedIterations);
+                return true;
+            }
             mState = FINISHED;
             return false;
         }
@@ -180,6 +203,15 @@
                             "Resume the benchmark before finishing each step.");
                 }
                 return true;
+            case RUNNING_CUSTOMIZED:
+                mCustomizedIterationListener.onFinished(mCustomizedIterations);
+                mCustomizedIterations++;
+                if (mCustomizedIterations >= mMaxCustomizedIterations) {
+                    mState = FINISHED;
+                    return false;
+                }
+                mCustomizedIterationListener.onStart(mCustomizedIterations);
+                return true;
             case FINISHED:
                 throw new IllegalStateException("The benchmark has finished.");
             default:
@@ -240,4 +272,13 @@
         status.putLong(key + "_standardDeviation", standardDeviation());
         instrumentation.sendStatus(Activity.RESULT_OK, status);
     }
+
+    /** The interface to receive the events of customized iteration. */
+    public interface CustomizedIterationListener {
+        /** The customized iteration starts. */
+        void onStart(int iteration);
+
+        /** The customized iteration finished. */
+        void onFinished(int iteration);
+    }
 }
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/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml
index 85fd7176c..95ede34 100644
--- a/apct-tests/perftests/windowmanager/AndroidManifest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml
@@ -29,5 +29,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.perftests.wm"/>
+        android:targetPackage="com.android.perftests.wm">
+        <meta-data android:name="listener" android:value="android.wm.WmPerfRunListener" />
+    </instrumentation>
 </manifest>
diff --git a/apct-tests/perftests/windowmanager/AndroidTest.xml b/apct-tests/perftests/windowmanager/AndroidTest.xml
index 0a80cf9..6ac9f93 100644
--- a/apct-tests/perftests/windowmanager/AndroidTest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidTest.xml
@@ -28,14 +28,40 @@
         <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.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" />
     </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/README.md b/apct-tests/perftests/windowmanager/README.md
index 05fa627..8b5292f 100644
--- a/apct-tests/perftests/windowmanager/README.md
+++ b/apct-tests/perftests/windowmanager/README.md
@@ -25,3 +25,11 @@
           com.android.perftests.wm/androidx.test.runner.AndroidJUnitRunner
 ```
 * `kill-bg` is optional.
+
+Test arguments
+ - kill-bg
+   * boolean: Kill background process before running test.
+ - profiling-iterations
+   * int: Run the extra iterations with enabling method profiling.
+ - profiling-sampling
+   * int: The interval (0=trace each method, default is 10) of sample profiling in microseconds.
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
index 4ed3b4e..5c09ec2 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
@@ -41,7 +41,8 @@
 
 /** Measure the performance of internal methods in window manager service by trace tag. */
 @LargeTest
-public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
+public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase
+        implements ManualBenchmarkState.CustomizedIterationListener {
     private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
 
     @Rule
@@ -68,6 +69,9 @@
             "finishActivity",
             "startActivityInner");
 
+    private boolean mIsProfiling;
+    private boolean mIsTraceStarted;
+
     @Test
     @ManualBenchmarkTest(
             targetTestDurationNs = 20 * TIME_1_S_IN_NS,
@@ -76,13 +80,13 @@
                             | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
     public void testLaunchAndFinishActivity() throws Throwable {
         final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        state.setCustomizedIterations(getProfilingIterations(), this);
         long measuredTimeNs = 0;
-        boolean isTraceStarted = false;
 
         while (state.keepRunning(measuredTimeNs)) {
-            if (!isTraceStarted && !state.isWarmingUp()) {
+            if (!mIsTraceStarted && !mIsProfiling && !state.isWarmingUp()) {
                 startAsyncAtrace();
-                isTraceStarted = true;
+                mIsTraceStarted = true;
             }
             final long startTime = SystemClock.elapsedRealtimeNanos();
             mActivityRule.launchActivity();
@@ -91,7 +95,9 @@
             measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
         }
 
-        stopAsyncAtrace();
+        if (mIsTraceStarted) {
+            stopAsyncAtrace();
+        }
 
         mTraceMarkParser.forAllSlices((key, slices) -> {
             for (TraceMarkSlice slice : slices) {
@@ -108,7 +114,7 @@
         SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
     }
 
-    private void stopAsyncAtrace() throws IOException {
+    private void stopAsyncAtrace() {
         final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
         final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
         try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
@@ -116,6 +122,28 @@
             while ((line = reader.readLine()) != null) {
                 mTraceMarkParser.visit(line);
             }
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to read the result of stopped atrace", e);
+        }
+    }
+
+    @Override
+    public void onStart(int iteration) {
+        if (mIsTraceStarted) {
+            // Do not capture trace when profiling because the result will be much slower.
+            stopAsyncAtrace();
+            mIsTraceStarted = false;
+        }
+        mIsProfiling = true;
+        startProfiling(InternalWindowOperationPerfTest.class.getSimpleName()
+                + "_MethodTracing_" + iteration + ".trace");
+    }
+
+    @Override
+    public void onFinished(int iteration) {
+        stopProfiling();
+        if (iteration >= getProfilingIterations() - 1) {
+            mIsProfiling = false;
         }
     }
 }
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
index 6122ef2..efcabd8 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
@@ -62,7 +62,8 @@
 
 @RunWith(Parameterized.class)
 @LargeTest
-public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
+public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase
+        implements ManualBenchmarkState.CustomizedIterationListener {
     private static Intent sRecentsIntent;
 
     @Rule
@@ -162,6 +163,7 @@
                     | StatsReport.FLAG_COEFFICIENT_VAR))
     public void testRecentsAnimation() throws Throwable {
         final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        state.setCustomizedIterations(getProfilingIterations(), this);
         final IActivityTaskManager atm = ActivityTaskManager.getService();
 
         final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
@@ -230,7 +232,21 @@
             state.addExtraResult("start", elapsedTimeNsOfStart);
 
             // Ensure the animation callback is done.
-            Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
+            Assume.assumeTrue(recentsSemaphore.tryAcquire(
+                    sIsProfilingMethod ? 10 * TIME_5_S_IN_NS : TIME_5_S_IN_NS,
+                    TimeUnit.NANOSECONDS));
         }
     }
+
+    @Override
+    public void onStart(int iteration) {
+        startProfiling(RecentsAnimationPerfTest.class.getSimpleName()
+                + "_interval_" + intervalBetweenOperations
+                + "_MethodTracing_" + iteration + ".trace");
+    }
+
+    @Override
+    public void onFinished(int iteration) {
+        stopProfiling();
+    }
 }
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index cff5663..2697428 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -54,7 +54,8 @@
 @RunWith(Parameterized.class)
 @LargeTest
 @Presubmit
-public class RelayoutPerfTest extends WindowManagerPerfTestBase {
+public class RelayoutPerfTest extends WindowManagerPerfTestBase
+        implements BenchmarkState.CustomizedIterationListener {
     private int mIteration;
 
     @Rule
@@ -93,9 +94,22 @@
         mActivityRule.runOnUiThread(() -> activity.setContentView(contentView));
         getInstrumentation().waitForIdleSync();
 
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        state.setCustomizedIterations(getProfilingIterations(), this);
         final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(),
                 () -> visibilities[mIteration++ % visibilities.length]);
-        relayoutRunner.runBenchmark(mPerfStatusReporter.getBenchmarkState());
+        relayoutRunner.runBenchmark(state);
+    }
+
+    @Override
+    public void onStart(int iteration) {
+        startProfiling(RelayoutPerfTest.class.getSimpleName() + "_" + testName
+                + "_MethodTracing_" + iteration + ".trace");
+    }
+
+    @Override
+    public void onFinished(int iteration) {
+        stopProfiling();
     }
 
     /** A dummy view to get IWindow. */
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index c72cc9d..c52b130 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -48,8 +48,6 @@
 public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
         implements ManualBenchmarkState.CustomizedIterationListener {
 
-    private static final int PROFILED_ITERATIONS = 2;
-
     @Rule
     public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
 
@@ -64,7 +62,7 @@
         sUiAutomation.dropShellPermissionIdentity();
     }
 
-    /** The last {@link #PROFILED_ITERATIONS} will provide the information of method profiling. */
+    /** The last customized iterations will provide the information of method profiling. */
     @Override
     public void onStart(int iteration) {
         startProfiling(WindowAddRemovePerfTest.class.getSimpleName()
@@ -80,7 +78,7 @@
     @ManualBenchmarkTest(warmupDurationNs = TIME_1_S_IN_NS, targetTestDurationNs = TIME_5_S_IN_NS)
     public void testAddRemoveWindow() throws Throwable {
         final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        state.setCustomizedIterations(PROFILED_ITERATIONS, this);
+        state.setCustomizedIterations(getProfilingIterations(), this);
         new TestWindow().runBenchmark(state);
     }
 
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index dc6245b..b51a9a8 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -32,6 +32,7 @@
 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import androidx.test.runner.lifecycle.Stage;
 
+import org.junit.After;
 import org.junit.BeforeClass;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -52,18 +53,17 @@
 
     /**
      * The out directory matching the directory-keys of collector in AndroidTest.xml. The directory
-     * is in /data because while enabling method profling of system server, it cannot write the
+     * is in /data because while enabling method profiling 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");
+
+    static boolean sIsProfilingMethod;
 
     @BeforeClass
     public static void setUpOnce() {
         final Context context = getInstrumentation().getContext();
 
-        if (!BASE_OUT_PATH.exists()) {
-            executeShellCommand("mkdir -p " + BASE_OUT_PATH);
-        }
         if (!context.getSystemService(PowerManager.class).isInteractive()
                 || context.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
             executeShellCommand("input keyevent KEYCODE_WAKEUP");
@@ -73,6 +73,14 @@
                 .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
     }
 
+    @After
+    public void tearDown() {
+        // Make sure that profiling is stopped if test fails.
+        if (sIsProfilingMethod) {
+            stopProfiling();
+        }
+    }
+
     /**
      * Executes shell command with reading the output. It may also used to block until the current
      * command is completed.
@@ -93,12 +101,26 @@
     }
 
     /** Starts method tracing on system server. */
-    void startProfiling(String subPath) {
-        executeShellCommand("am profile start system " + new File(BASE_OUT_PATH, subPath));
+    static void startProfiling(String subPath) {
+        if (!BASE_OUT_PATH.exists()) {
+            executeShellCommand("mkdir -p " + BASE_OUT_PATH);
+        }
+        final String samplingArg = WmPerfRunListener.sSamplingIntervalUs > 0
+                ? ("--sampling " + WmPerfRunListener.sSamplingIntervalUs)
+                : "";
+        executeShellCommand("am profile start " + samplingArg + " system "
+                + new File(BASE_OUT_PATH, subPath));
+        sIsProfilingMethod = true;
     }
 
-    void stopProfiling() {
+    static void stopProfiling() {
         executeShellCommand("am profile stop system");
+        sIsProfilingMethod = false;
+    }
+
+    /** Returns how many iterations should run with method tracing. */
+    static int getProfilingIterations() {
+        return WmPerfRunListener.sProfilingIterations;
     }
 
     static void runWithShellPermissionIdentity(Runnable runnable) {
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
index 6eb85aa..d955289 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.WindowManagerPolicyConstants;
 import android.wm.WindowManagerPerfTestBase.SettingsSession;
 
@@ -46,10 +47,22 @@
 
 /** Prepare the preconditions before running performance test. */
 public class WmPerfRunListener extends RunListener {
+    private static final String TAG = WmPerfRunListener.class.getSimpleName();
 
-    private static final String OPTION_KILL_BACKGROUND = "kill-bg";
+    private static final String ARGUMENT_LOG_ONLY = "log";
+    private static final String ARGUMENT_KILL_BACKGROUND = "kill-bg";
+    private static final String ARGUMENT_PROFILING_ITERATIONS = "profiling-iterations";
+    private static final String ARGUMENT_PROFILING_SAMPLING = "profiling-sampling";
+    private static final String DEFAULT_PROFILING_ITERATIONS = "0";
+    private static final String DEFAULT_PROFILING_SAMPLING_US = "10";
     private static final long KILL_BACKGROUND_WAIT_MS = 3000;
 
+    /** The requested iterations to run with method profiling. */
+    static int sProfilingIterations;
+
+    /** The interval of sample profiling in microseconds. */
+    static int sSamplingIntervalUs;
+
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
     private long mWaitPreconditionDoneMs = 500;
 
@@ -83,6 +96,16 @@
     @Override
     public void testRunStarted(Description description) {
         final Bundle arguments = InstrumentationRegistry.getArguments();
+        // If true, it only logs the method names without running.
+        final boolean skip = Boolean.parseBoolean(arguments.getString(ARGUMENT_LOG_ONLY, "false"));
+        Log.i(TAG, "arguments=" + arguments);
+        if (skip) {
+            return;
+        }
+        sProfilingIterations = Integer.parseInt(
+                arguments.getString(ARGUMENT_PROFILING_ITERATIONS, DEFAULT_PROFILING_ITERATIONS));
+        sSamplingIntervalUs = Integer.parseInt(
+                arguments.getString(ARGUMENT_PROFILING_SAMPLING, DEFAULT_PROFILING_SAMPLING_US));
 
         // Use gesture navigation for consistency.
         mNavigationModeSetting.set(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL);
@@ -97,7 +120,7 @@
         });
         PhoneWindow.sendCloseSystemWindows(mContext, "WmPerfTests");
 
-        if (Boolean.parseBoolean(arguments.getString(OPTION_KILL_BACKGROUND))) {
+        if (Boolean.parseBoolean(arguments.getString(ARGUMENT_KILL_BACKGROUND))) {
             runWithShellPermissionIdentity(this::killBackgroundProcesses);
             mWaitPreconditionDoneMs = KILL_BACKGROUND_WAIT_MS;
         }
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 3234b27..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,6 +85,7 @@
 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;
@@ -329,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();
                 }
             }
         }
@@ -376,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;
         }
 
         /**
@@ -430,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);
         }
     }
@@ -476,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 =
@@ -500,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";
@@ -530,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;
@@ -543,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.
@@ -564,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
@@ -652,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);
         }
@@ -731,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();
 
@@ -760,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);
 
@@ -1407,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);
@@ -1521,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();
             }
@@ -1693,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;
@@ -1704,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 532c3d9..e5baa7c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11617,6 +11617,16 @@
     field public int version;
   }
 
+  public final class FileChecksum implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getKind();
+    method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+    method @Nullable public String getSplitName();
+    method @NonNull public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.FileChecksum> CREATOR;
+  }
+
   public final class InstallSourceInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getInitiatingPackageName();
@@ -11901,8 +11911,8 @@
     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_OTHER_ERROR = 4; // 0x4
     field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
     field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
   }
@@ -11992,6 +12002,7 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
+    method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
     method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
@@ -12082,6 +12093,7 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field public static final int DONT_KILL_APP = 1; // 0x1
+    field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
     field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays";
@@ -12236,6 +12248,8 @@
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
+    field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
+    field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
@@ -12245,9 +12259,16 @@
     field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
     field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
     field public static final int SYNCHRONOUS = 2; // 0x2
+    field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
+    field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
+    field public static final int WHOLE_MD5 = 2; // 0x2
+    field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
+    field public static final int WHOLE_SHA1 = 4; // 0x4
+    field public static final int WHOLE_SHA256 = 8; // 0x8
+    field public static final int WHOLE_SHA512 = 16; // 0x10
   }
 
   public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
@@ -14250,6 +14271,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);
@@ -31620,6 +31645,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();
@@ -31646,6 +31672,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);
@@ -36733,9 +36760,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();
@@ -46987,7 +47016,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;
   }
@@ -47836,6 +47865,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);
@@ -48352,7 +48382,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();
@@ -48374,6 +48406,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();
@@ -48421,6 +48454,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";
@@ -55185,7 +55222,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();
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 7cd8a62..17545a4 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -46,6 +46,13 @@
     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 {
diff --git a/api/system-current.txt b/api/system-current.txt
index ea16238..00a59bf 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -219,6 +219,7 @@
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+    field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
@@ -8394,6 +8395,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);
@@ -10816,7 +10818,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);
@@ -11233,10 +11236,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();
@@ -11268,7 +11269,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);
@@ -11306,10 +11306,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";
@@ -12171,6 +12167,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
@@ -12743,8 +12740,8 @@
   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(long);
-    method public default long getNetworkHandle();
+    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);
   }
@@ -12885,7 +12882,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(long);
+    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 a1d1fa7..529dcf7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1305,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
   }
 
 }
@@ -2797,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
   }
@@ -3236,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";
@@ -4071,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
@@ -4618,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
@@ -5571,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/idmap2/tests/ResultTests.cpp b/cmds/idmap2/tests/ResultTests.cpp
index cbced0a..f2f8854 100644
--- a/cmds/idmap2/tests/ResultTests.cpp
+++ b/cmds/idmap2/tests/ResultTests.cpp
@@ -260,7 +260,8 @@
 
 struct NoCopyContainer {
   uint32_t value;  // NOLINT(misc-non-private-member-variables-in-classes)
-  DISALLOW_COPY_AND_ASSIGN(NoCopyContainer);
+  NoCopyContainer(const NoCopyContainer&) = delete;
+  NoCopyContainer& operator=(const NoCopyContainer&) = delete;
 };
 
 Result<std::unique_ptr<NoCopyContainer>> CreateNoCopyContainer(bool succeed) {
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/statsd/Android.bp b/cmds/statsd/Android.bp
index 7c41951..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",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6b335ee..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;
 }
@@ -5054,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..4574b2e 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -25,8 +25,9 @@
 using std::unordered_map;
 using std::vector;
 
-CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index)
-    : ConditionTracker(id, index) {
+CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index,
+                                                         const uint64_t protoHash)
+    : ConditionTracker(id, index, protoHash) {
     VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId);
 }
 
@@ -38,9 +39,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 +89,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 +106,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;
@@ -109,6 +123,49 @@
     return true;
 }
 
+bool CombinationConditionTracker::onConfigUpdated(
+        const vector<Predicate>& allConditionProtos, const int index,
+        const vector<sp<ConditionTracker>>& allConditionTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        const unordered_map<int64_t, int>& conditionTrackerMap) {
+    ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers,
+                                      atomMatchingTrackerMap, conditionTrackerMap);
+    mTrackerIndex.clear();
+    mChildren.clear();
+    mUnSlicedChildren.clear();
+    mSlicedChildren.clear();
+    Predicate_Combination combinationCondition = allConditionProtos[mIndex].combination();
+
+    for (const int64_t child : combinationCondition.predicate()) {
+        const auto& it = conditionTrackerMap.find(child);
+
+        if (it == conditionTrackerMap.end()) {
+            ALOGW("Predicate %lld not found in the config", (long long)child);
+            return false;
+        }
+
+        int childIndex = it->second;
+        const sp<ConditionTracker>& childTracker = allConditionTrackers[childIndex];
+
+        // Ensures that the child's tracker indices are updated.
+        if (!childTracker->onConfigUpdated(allConditionProtos, childIndex, allConditionTrackers,
+                                           atomMatchingTrackerMap, conditionTrackerMap)) {
+            ALOGW("Child update failed %lld ", (long long)child);
+            return false;
+        }
+
+        if (allConditionTrackers[childIndex]->isSliced()) {
+            mSlicedChildren.push_back(childIndex);
+        } else {
+            mUnSlicedChildren.push_back(childIndex);
+        }
+        mChildren.push_back(childIndex);
+        mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(),
+                             childTracker->getAtomMatchingTrackerIndex().end());
+    }
+    return true;
+}
+
 void CombinationConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
         const bool isPartialLink,
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 39ff0ab..672d61c 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -24,16 +24,21 @@
 namespace os {
 namespace statsd {
 
-class CombinationConditionTracker : public virtual ConditionTracker {
+class CombinationConditionTracker : public ConditionTracker {
 public:
-    CombinationConditionTracker(const int64_t& id, const int index);
+    CombinationConditionTracker(const int64_t& id, const int index, const uint64_t protoHash);
 
     ~CombinationConditionTracker();
 
     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;
+
+    bool onConfigUpdated(const std::vector<Predicate>& allConditionProtos, const int index,
+                         const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                         const std::unordered_map<int64_t, int>& conditionTrackerMap) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
@@ -102,6 +107,7 @@
     std::vector<int> mSlicedChildren;
     std::vector<int> mUnSlicedChildren;
 
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 62736c8..3bf4e63 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>
@@ -31,38 +31,59 @@
 
 class ConditionTracker : public virtual RefBase {
 public:
-    ConditionTracker(const int64_t& id, const int index)
+    ConditionTracker(const int64_t& id, const int index, const uint64_t protoHash)
         : mConditionId(id),
           mIndex(index),
           mInitialized(false),
           mTrackerIndex(),
           mUnSlicedPartCondition(ConditionState::kUnknown),
-          mSliced(false){};
+          mSliced(false),
+          mProtoHash(protoHash){};
 
     virtual ~ConditionTracker(){};
 
-    inline const int64_t& getId() { return mConditionId; }
-
     // 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)
+    //                       need to call init() on child 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;
+
+    // Update appropriate state on config updates. Primarily, all indices need to be updated.
+    // This predicate and all of its children are guaranteed to be preserved across the update.
+    // This function is recursive and will call onConfigUpdated on child conditions. It does not
+    // manage cycle detection since all preserved conditions should not have any cycles.
+    //
+    // allConditionProtos: the new predicates.
+    // index: the new index of this tracker in allConditionProtos and allConditionTrackers.
+    // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
+    //                       need to call onConfigUpdated() on child conditions)
+    // [atomMatchingTrackerMap]: map of atom matcher id to index after the config update
+    // [conditionTrackerMap]: map of condition tracker id to index after the config update.
+    // returns whether or not the update is successful
+    virtual bool onConfigUpdated(const std::vector<Predicate>& allConditionProtos, const int index,
+                                 const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                                 const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                                 const std::unordered_map<int64_t, int>& conditionTrackerMap) {
+        mIndex = index;
+        return true;
+    }
 
     // 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 +109,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;
     }
 
@@ -110,6 +131,10 @@
         return mConditionId;
     }
 
+    inline uint64_t getProtoHash() const {
+        return mProtoHash;
+    }
+
     virtual void getTrueSlicedDimensions(
         const std::vector<sp<ConditionTracker>>& allConditions,
         std::set<HashableDimensionKey>* dimensions) const = 0;
@@ -131,12 +156,12 @@
     const int64_t mConditionId;
 
     // the index of this condition in the manager's condition list.
-    const int mIndex;
+    int mIndex;
 
     // 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.
@@ -149,6 +174,12 @@
     ConditionState mUnSlicedPartCondition;
 
     bool mSliced;
+
+    // Hash of the Predicate's proto bytes from StatsdConfig.
+    // Used to determine if the definition of this condition has changed across a config update.
+    const uint64_t mProtoHash;
+
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index efb4d49..1dcc8f9 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -27,54 +27,21 @@
 using std::unordered_map;
 
 SimpleConditionTracker::SimpleConditionTracker(
-        const ConfigKey& key, const int64_t& id, const int index,
+        const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index,
         const SimplePredicate& simplePredicate,
-        const unordered_map<int64_t, int>& trackerNameIndexMap)
-    : ConditionTracker(id, index), mConfigKey(key), mContainANYPositionInInternalDimensions(false) {
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap)
+    : ConditionTracker(id, index, protoHash),
+      mConfigKey(key),
+      mContainANYPositionInInternalDimensions(false) {
     VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
     mCountNesting = simplePredicate.count_nesting();
 
-    if (simplePredicate.has_start()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.start());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
-            return;
-        }
-        mStartLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStartLogMatcherIndex);
-    } else {
-        mStartLogMatcherIndex = -1;
-    }
-
-    if (simplePredicate.has_stop()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.stop());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop());
-            return;
-        }
-        mStopLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStopLogMatcherIndex);
-    } else {
-        mStopLogMatcherIndex = -1;
-    }
-
-    if (simplePredicate.has_stop_all()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.stop_all());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all());
-            return;
-        }
-        mStopAllLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStopAllLogMatcherIndex);
-    } else {
-        mStopAllLogMatcherIndex = -1;
-    }
+    setMatcherIndices(simplePredicate, atomMatchingTrackerMap);
 
     if (simplePredicate.has_dimensions()) {
         translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
         if (mOutputDimensions.size() > 0) {
             mSliced = true;
-            mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
         }
         mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions());
     }
@@ -95,14 +62,70 @@
 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;
 }
 
+bool SimpleConditionTracker::onConfigUpdated(
+        const vector<Predicate>& allConditionProtos, const int index,
+        const vector<sp<ConditionTracker>>& allConditionTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        const unordered_map<int64_t, int>& conditionTrackerMap) {
+    ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers,
+                                      atomMatchingTrackerMap, conditionTrackerMap);
+    setMatcherIndices(allConditionProtos[index].simple_predicate(), atomMatchingTrackerMap);
+    return true;
+}
+
+void SimpleConditionTracker::setMatcherIndices(
+        const SimplePredicate& simplePredicate,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
+    mTrackerIndex.clear();
+    if (simplePredicate.has_start()) {
+        auto pair = atomMatchingTrackerMap.find(simplePredicate.start());
+        if (pair == atomMatchingTrackerMap.end()) {
+            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
+            return;
+        }
+        mStartLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStartLogMatcherIndex);
+    } else {
+        mStartLogMatcherIndex = -1;
+    }
+
+    if (simplePredicate.has_stop()) {
+        auto pair = atomMatchingTrackerMap.find(simplePredicate.stop());
+        if (pair == atomMatchingTrackerMap.end()) {
+            ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop());
+            return;
+        }
+        mStopLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStopLogMatcherIndex);
+    } else {
+        mStopLogMatcherIndex = -1;
+    }
+
+    if (simplePredicate.has_stop_all()) {
+        auto pair = atomMatchingTrackerMap.find(simplePredicate.stop_all());
+        if (pair == atomMatchingTrackerMap.end()) {
+            ALOGW("Stop all matcher %lld found in the config",
+                  (long long)simplePredicate.stop_all());
+            return;
+        }
+        mStopAllLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStopAllLogMatcherIndex);
+    } else {
+        mStopAllLogMatcherIndex = -1;
+    }
+}
+
 void SimpleConditionTracker::dumpState() {
     VLOG("%lld DUMP:", (long long)mConditionId);
     for (const auto& pair : mSlicedConditionState) {
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index ea7f87b..7a8b4010 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -27,18 +27,23 @@
 namespace os {
 namespace statsd {
 
-class SimpleConditionTracker : public virtual ConditionTracker {
+class SimpleConditionTracker : public ConditionTracker {
 public:
-    SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
-                           const SimplePredicate& simplePredicate,
-                           const std::unordered_map<int64_t, int>& trackerNameIndexMap);
+    SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const uint64_t protoHash,
+                           const int index, const SimplePredicate& simplePredicate,
+                           const std::unordered_map<int64_t, int>& atomMatchingTrackerMap);
 
     ~SimpleConditionTracker();
 
     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;
+
+    bool onConfigUpdated(const std::vector<Predicate>& allConditionProtos, const int index,
+                         const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                         const std::unordered_map<int64_t, int>& conditionTrackerMap) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
@@ -112,10 +117,11 @@
     std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
     std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
 
-    int mDimensionTag;
-
     std::map<HashableDimensionKey, int> mSlicedConditionState;
 
+    void setMatcherIndices(const SimplePredicate& predicate,
+                           const std::unordered_map<int64_t, int>& logTrackerMap);
+
     void handleStopAll(std::vector<ConditionState>& conditionCache,
                        std::vector<bool>& changedCache);
 
@@ -129,6 +135,7 @@
     FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
     FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
     FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions);
 };
 
 }  // namespace statsd
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 60bcc26..0000000
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ /dev/null
@@ -1,121 +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,
-                                                             const uint64_t protoHash)
-    : LogMatchingTracker(id, index, protoHash) {
-}
-
-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 6b8a7fb..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, const uint64_t protoHash);
-
-    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 49a41ad..0000000
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ /dev/null
@@ -1,108 +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, const uint64_t protoHash)
-        : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){};
-
-    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;
-    }
-
-    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 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;
-
-    // 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, TestCreateLogTrackerSimple);
-    FRIEND_TEST(MetricsManagerTest, TestCreateLogTrackerCombination);
-    FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers);
-};
-
-}  // 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 ff47d35..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 uint64_t protoHash,
-                                                   const SimpleAtomMatcher& matcher,
-                                                   const sp<UidMap>& uidMap)
-    : LogMatchingTracker(id, index, protoHash), 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 e58e012..0000000
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ /dev/null
@@ -1,54 +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 uint64_t protoHash,
-                             const SimpleAtomMatcher& matcher, const sp<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 sp<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 e6d9122..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"
 
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 2c3deca..a0c701e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -24,8 +24,8 @@
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "guardrail/StatsdStats.h"
-#include "matchers/CombinationLogMatchingTracker.h"
-#include "matchers/SimpleLogMatchingTracker.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"
@@ -77,14 +77,14 @@
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
 
-    mConfigValid =
-            initStatsdConfig(key, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                             periodicAlarmMonitor, timeBaseNs, currentTimeNs, mTagIds,
-                             mAllAtomMatchers, mLogTrackerMap, mAllConditionTrackers,
-                             mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
-                             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
-                             mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
-                             mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
+    mConfigValid = initStatsdConfig(
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
+            mAllConditionTrackers, mConditionTrackerMap, 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();
@@ -93,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()) {
@@ -155,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;
     }
@@ -175,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()) {
@@ -201,15 +202,22 @@
                                   const int64_t currentTimeNs,
                                   const sp<AlarmMonitor>& anomalyAlarmMonitor,
                                   const sp<AlarmMonitor>& periodicAlarmMonitor) {
-    vector<sp<LogMatchingTracker>> newAtomMatchers;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    vector<sp<ConditionTracker>> newConditionTrackers;
+    unordered_map<int64_t, int> newConditionTrackerMap;
     mTagIds.clear();
-    mConfigValid =
-            updateStatsdConfig(mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor,
-                               periodicAlarmMonitor, timeBaseNs, currentTimeNs, mAllAtomMatchers,
-                               mLogTrackerMap, mTagIds, newAtomMatchers, newLogTrackerMap);
-    mAllAtomMatchers = newAtomMatchers;
-    mLogTrackerMap = newLogTrackerMap;
+    mTrackerToConditionMap.clear();
+    mConfigValid = updateStatsdConfig(
+            mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
+            mAllConditionTrackers, mConditionTrackerMap, mTagIds, newAtomMatchingTrackers,
+            newAtomMatchingTrackerMap, newConditionTrackers, newConditionTrackerMap,
+            mTrackerToConditionMap);
+    mAllAtomMatchingTrackers = newAtomMatchingTrackers;
+    mAtomMatchingTrackerMap = newAtomMatchingTrackerMap;
+    mAllConditionTrackers = newConditionTrackers;
+    mConditionTrackerMap = newConditionTrackerMap;
     return mConfigValid;
 }
 
@@ -502,11 +510,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.
@@ -596,10 +605,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 6f9a2336..bd0c816 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"
 
@@ -217,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
@@ -225,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;
@@ -239,29 +239,32 @@
     // Hold all periodic alarm trackers.
     std::vector<sp<AlarmTracker>> mAllPeriodicAlarmTrackers;
 
-    // To make updating configs faster, we map the id of a LogMatchingTracker, MetricProducer, and
+    // 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 mAllAtomMatchers.
-    std::unordered_map<int64_t, int> mLogTrackerMap;
+    // Maps the id of an atom matching tracker to its index in mAllAtomMatchingTrackers.
+    std::unordered_map<int64_t, int> mAtomMatchingTrackerMap;
+
+    // Maps the id of a condition tracker to its index in mAllConditionTrackers.
+    std::unordered_map<int64_t, int> mConditionTrackerMap;
 
     // 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/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 9b684f1..dcfbd5e 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -411,7 +411,7 @@
 
 void ValueMetricProducer::resetBase() {
     for (auto& slice : mCurrentBaseInfo) {
-        for (auto& baseInfo : slice.second) {
+        for (auto& baseInfo : slice.second.baseInfos) {
             baseInfo.hasBase = false;
         }
     }
@@ -623,7 +623,7 @@
                 mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end();
         if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) {
             auto it = mCurrentBaseInfo.find(whatKey);
-            for (auto& baseInfo : it->second) {
+            for (auto& baseInfo : it->second.baseInfos) {
                 baseInfo.hasBase = false;
             }
         }
@@ -652,7 +652,7 @@
             (unsigned long)mCurrentSlicedBucket.size());
     if (verbose) {
         for (const auto& it : mCurrentSlicedBucket) {
-          for (const auto& interval : it.second) {
+          for (const auto& interval : it.second.intervals) {
               fprintf(out, "\t(what)%s\t(states)%s  (value)%s\n",
                       it.first.getDimensionKeyInWhat().toString().c_str(),
                       it.first.getStateValuesKey().toString().c_str(),
@@ -788,23 +788,23 @@
         return;
     }
 
-    vector<BaseInfo>& baseInfos = mCurrentBaseInfo[whatKey];
+    DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey];
+    vector<BaseInfo>& baseInfos = dimensionsInWhatInfo.baseInfos;
     if (baseInfos.size() < mFieldMatchers.size()) {
         VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
         baseInfos.resize(mFieldMatchers.size());
     }
 
-    for (BaseInfo& baseInfo : baseInfos) {
-        if (!baseInfo.hasCurrentState) {
-            baseInfo.currentState = getUnknownStateKey();
-            baseInfo.hasCurrentState = true;
-        }
+    if (!dimensionsInWhatInfo.hasCurrentState) {
+        dimensionsInWhatInfo.currentState = getUnknownStateKey();
+        dimensionsInWhatInfo.hasCurrentState = true;
     }
 
     // We need to get the intervals stored with the previous state key so we can
     // close these value intervals.
-    const auto oldStateKey = baseInfos[0].currentState;
-    vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)];
+    const auto oldStateKey = dimensionsInWhatInfo.currentState;
+    vector<Interval>& intervals =
+            mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals;
     if (intervals.size() < mFieldMatchers.size()) {
         VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
         intervals.resize(mFieldMatchers.size());
@@ -818,14 +818,14 @@
     // Discussion here: http://ag/6124370.
     bool useAnomalyDetection = true;
 
+    dimensionsInWhatInfo.hasCurrentState = true;
+    dimensionsInWhatInfo.currentState = stateKey;
     for (int i = 0; i < (int)mFieldMatchers.size(); i++) {
         const Matcher& matcher = mFieldMatchers[i];
         BaseInfo& baseInfo = baseInfos[i];
         Interval& interval = intervals[i];
         interval.valueIndex = i;
         Value value;
-        baseInfo.hasCurrentState = true;
-        baseInfo.currentState = stateKey;
         if (!getDoubleOrLong(event, matcher, value)) {
             VLOG("Failed to get value %d from event %s", i, event.ToString().c_str());
             StatsdStats::getInstance().noteBadValueType(mMetricId);
@@ -990,7 +990,7 @@
         bool bucketHasData = false;
         // The current bucket is large enough to keep.
         for (const auto& slice : mCurrentSlicedBucket) {
-            ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second);
+            PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals);
             bucket.mConditionTrueNs = conditionTrueDuration;
             // it will auto create new vector of ValuebucketInfo if the key is not found.
             if (bucket.valueIndex.size() > 0) {
@@ -1030,9 +1030,9 @@
     mCurrentBucketNum += numBucketsForward;
 }
 
-ValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime,
-                                                    const std::vector<Interval>& intervals) {
-    ValueBucket bucket;
+PastValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime,
+                                                        const std::vector<Interval>& intervals) {
+    PastValueBucket bucket;
     bucket.mBucketStartNs = mCurrentBucketStartTimeNs;
     bucket.mBucketEndNs = bucketEndTime;
     for (const auto& interval : intervals) {
@@ -1059,7 +1059,7 @@
     // Cleanup data structure to aggregate values.
     for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) {
         bool obsolete = true;
-        for (auto& interval : it->second) {
+        for (auto& interval : it->second.intervals) {
             interval.hasValue = false;
             interval.sampleSize = 0;
             if (interval.seenNewData) {
@@ -1107,7 +1107,7 @@
                     continue;
                 }
                 // TODO: fix this when anomaly can accept double values
-                auto& interval = slice.second[0];
+                auto& interval = slice.second.intervals[0];
                 if (interval.hasValue) {
                     mCurrentFullBucket[slice.first] += interval.value.long_value;
                 }
@@ -1126,7 +1126,7 @@
                 for (auto& tracker : mAnomalyTrackers) {
                     if (tracker != nullptr) {
                         // TODO: fix this when anomaly can accept double values
-                        auto& interval = slice.second[0];
+                        auto& interval = slice.second.intervals[0];
                         if (interval.hasValue) {
                             tracker->addPastBucket(slice.first, interval.value.long_value,
                                                    mCurrentBucketNum);
@@ -1139,7 +1139,7 @@
         // Accumulate partial bucket.
         for (const auto& slice : mCurrentSlicedBucket) {
             // TODO: fix this when anomaly can accept double values
-            auto& interval = slice.second[0];
+            auto& interval = slice.second.intervals[0];
             if (interval.hasValue) {
                 mCurrentFullBucket[slice.first] += interval.value.long_value;
             }
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index e72002e..472cc33 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -31,7 +31,7 @@
 namespace os {
 namespace statsd {
 
-struct ValueBucket {
+struct PastValueBucket {
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     std::vector<int> valueIndex;
@@ -41,7 +41,6 @@
     int64_t mConditionTrueNs;
 };
 
-
 // Aggregates values within buckets.
 //
 // There are different events that might complete a bucket
@@ -173,7 +172,7 @@
     // if this is pulled metric
     const bool mIsPulled;
 
-    // internal state of an ongoing aggregation bucket.
+    // Tracks the value information of one value field.
     typedef struct {
         // Index in multi value aggregation.
         int valueIndex;
@@ -188,25 +187,40 @@
         bool seenNewData = false;
     } Interval;
 
+    // Internal state of an ongoing aggregation bucket.
+    typedef struct CurrentValueBucket {
+        // Value information for each value field of the metric.
+        std::vector<Interval> intervals;
+    } CurrentValueBucket;
+
+    // Holds base information for diffing values from one value field.
     typedef struct {
         // Holds current base value of the dimension. Take diff and update if necessary.
         Value base;
         // Whether there is a base to diff to.
         bool hasBase;
+    } BaseInfo;
+
+    // State key and base information for a specific DimensionsInWhat key.
+    typedef struct {
+        std::vector<BaseInfo> baseInfos;
         // Last seen state value(s).
         HashableDimensionKey currentState;
         // Whether this dimensions in what key has a current state key.
         bool hasCurrentState;
-    } BaseInfo;
+    } DimensionsInWhatInfo;
 
-    std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket;
+    // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat
+    // key and StateValuesKey pair.
+    std::unordered_map<MetricDimensionKey, CurrentValueBucket> mCurrentSlicedBucket;
 
-    std::unordered_map<HashableDimensionKey, std::vector<BaseInfo>> mCurrentBaseInfo;
+    // Tracks current state key and base information for each DimensionsInWhat key.
+    std::unordered_map<HashableDimensionKey, DimensionsInWhatInfo> mCurrentBaseInfo;
 
     std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
 
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
-    std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
+    std::unordered_map<MetricDimensionKey, std::vector<PastValueBucket>> mPastBuckets;
 
     const int64_t mMinBucketSizeNs;
 
@@ -224,8 +238,8 @@
     void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData,
                           int64_t originalPullTimeNs, int64_t eventElapsedTimeNs);
 
-    ValueBucket buildPartialBucket(int64_t bucketEndTime,
-                                   const std::vector<Interval>& intervals);
+    PastValueBucket buildPartialBucket(int64_t bucketEndTime,
+                                       const std::vector<Interval>& intervals);
 
     void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs);
 
@@ -234,7 +248,7 @@
     // Reset diff base and mHasGlobalBase
     void resetBase();
 
-    static const size_t kBucketSize = sizeof(ValueBucket{});
+    static const size_t kBucketSize = sizeof(PastValueBucket{});
 
     const size_t mDimensionSoftLimit;
 
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 562e291..bd60b6b 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -15,6 +15,7 @@
  */
 
 #define DEBUG false  // STOPSHIP if true
+#include "Log.h"
 
 #include "config_update_utils.h"
 
@@ -29,9 +30,9 @@
 // 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>& oldLogTrackerMap,
-                                  const vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                                  const unordered_map<int64_t, int>& newLogTrackerMap,
+                                  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.
@@ -42,9 +43,9 @@
     const AtomMatcher& matcher = config.atom_matcher(matcherIdx);
     int64_t id = matcher.id();
     // Check if new matcher.
-    const auto& oldLogTrackerIt = oldLogTrackerMap.find(id);
-    if (oldLogTrackerIt == oldLogTrackerMap.end()) {
-        matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
+    const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id);
+    if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) {
+        matchersToUpdate[matcherIdx] = UPDATE_NEW;
         return true;
     }
 
@@ -55,7 +56,7 @@
         return false;
     }
     uint64_t newProtoHash = Hash64(serializedMatcher);
-    if (newProtoHash != oldAtomMatchers[oldLogTrackerIt->second]->getProtoHash()) {
+    if (newProtoHash != oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]->getProtoHash()) {
         matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
         return true;
     }
@@ -70,8 +71,8 @@
             cycleTracker[matcherIdx] = true;
             UpdateStatus status = UPDATE_PRESERVE;
             for (const int64_t childMatcherId : matcher.combination().matcher()) {
-                const auto& childIt = newLogTrackerMap.find(childMatcherId);
-                if (childIt == newLogTrackerMap.end()) {
+                const auto& childIt = newAtomMatchingTrackerMap.find(childMatcherId);
+                if (childIt == newAtomMatchingTrackerMap.end()) {
                     ALOGW("Matcher %lld not found in the config", (long long)childMatcherId);
                     return false;
                 }
@@ -80,9 +81,9 @@
                     ALOGE("Cycle detected in matcher config");
                     return false;
                 }
-                if (!determineMatcherUpdateStatus(config, childIdx, oldLogTrackerMap,
-                                                  oldAtomMatchers, newLogTrackerMap,
-                                                  matchersToUpdate, cycleTracker)) {
+                if (!determineMatcherUpdateStatus(
+                            config, childIdx, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
+                            newAtomMatchingTrackerMap, matchersToUpdate, cycleTracker)) {
                     return false;
                 }
 
@@ -103,25 +104,27 @@
     return true;
 }
 
-bool updateLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                       const unordered_map<int64_t, int>& oldLogTrackerMap,
-                       const vector<sp<LogMatchingTracker>>& oldAtomMatchers, set<int>& allTagIds,
-                       unordered_map<int64_t, int>& newLogTrackerMap,
-                       vector<sp<LogMatchingTracker>>& newAtomMatchers) {
+bool updateAtomMatchingTrackers(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,
+                                set<int64_t>& replacedMatchers) {
     const int atomMatcherCount = config.atom_matcher_size();
 
     vector<AtomMatcher> matcherProtos;
     matcherProtos.reserve(atomMatcherCount);
-    newAtomMatchers.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 (newLogTrackerMap.find(matcher.id()) != newLogTrackerMap.end()) {
+        if (newAtomMatchingTrackerMap.find(matcher.id()) != newAtomMatchingTrackerMap.end()) {
             ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id());
             return false;
         }
-        newLogTrackerMap[matcher.id()] = i;
+        newAtomMatchingTrackerMap[matcher.id()] = i;
         matcherProtos.push_back(matcher);
     }
 
@@ -129,8 +132,9 @@
     vector<UpdateStatus> matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(atomMatcherCount, false);
     for (int i = 0; i < atomMatcherCount; i++) {
-        if (!determineMatcherUpdateStatus(config, i, oldLogTrackerMap, oldAtomMatchers,
-                                          newLogTrackerMap, matchersToUpdate, cycleTracker)) {
+        if (!determineMatcherUpdateStatus(config, i, oldAtomMatchingTrackerMap,
+                                          oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                          matchersToUpdate, cycleTracker)) {
             return false;
         }
     }
@@ -140,23 +144,31 @@
         const int64_t id = matcher.id();
         switch (matchersToUpdate[i]) {
             case UPDATE_PRESERVE: {
-                const auto& oldLogTrackerIt = oldLogTrackerMap.find(id);
-                if (oldLogTrackerIt == oldLogTrackerMap.end()) {
+                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 int oldIndex = oldLogTrackerIt->second;
-                newAtomMatchers.push_back(oldAtomMatchers[oldIndex]);
+                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<LogMatchingTracker> tracker = createLogTracker(matcher, i, uidMap);
+            case UPDATE_REPLACE:
+                replacedMatchers.insert(id);
+                [[fallthrough]];  // Intentionally fallthrough to create the new matcher.
+            case UPDATE_NEW: {
+                sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, i, uidMap);
                 if (tracker == nullptr) {
                     return false;
                 }
-                newAtomMatchers.push_back(tracker);
+                newAtomMatchingTrackers.push_back(tracker);
                 break;
             }
             default: {
@@ -168,8 +180,9 @@
     }
 
     std::fill(cycleTracker.begin(), cycleTracker.end(), false);
-    for (auto& matcher : newAtomMatchers) {
-        if (!matcher->init(matcherProtos, newAtomMatchers, newLogTrackerMap, cycleTracker)) {
+    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.
@@ -180,24 +193,246 @@
     return true;
 }
 
+// Recursive function to determine if a condition needs to be updated. Populates conditionsToUpdate.
+// Returns whether the function was successful or not.
+bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx,
+                                    const unordered_map<int64_t, int>& oldConditionTrackerMap,
+                                    const vector<sp<ConditionTracker>>& oldConditionTrackers,
+                                    const unordered_map<int64_t, int>& newConditionTrackerMap,
+                                    const set<int64_t>& replacedMatchers,
+                                    vector<UpdateStatus>& conditionsToUpdate,
+                                    vector<bool>& cycleTracker) {
+    // Have already examined this condition.
+    if (conditionsToUpdate[conditionIdx] != UPDATE_UNKNOWN) {
+        return true;
+    }
+
+    const Predicate& predicate = config.predicate(conditionIdx);
+    int64_t id = predicate.id();
+    // Check if new condition.
+    const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id);
+    if (oldConditionTrackerIt == oldConditionTrackerMap.end()) {
+        conditionsToUpdate[conditionIdx] = UPDATE_NEW;
+        return true;
+    }
+
+    // This is an existing condition. Check if it has changed.
+    string serializedCondition;
+    if (!predicate.SerializeToString(&serializedCondition)) {
+        ALOGE("Unable to serialize matcher %lld", (long long)id);
+        return false;
+    }
+    uint64_t newProtoHash = Hash64(serializedCondition);
+    if (newProtoHash != oldConditionTrackers[oldConditionTrackerIt->second]->getProtoHash()) {
+        conditionsToUpdate[conditionIdx] = UPDATE_REPLACE;
+        return true;
+    }
+
+    switch (predicate.contents_case()) {
+        case Predicate::ContentsCase::kSimplePredicate: {
+            // Need to check if any of the underlying matchers changed.
+            const SimplePredicate& simplePredicate = predicate.simple_predicate();
+            if (simplePredicate.has_start()) {
+                if (replacedMatchers.find(simplePredicate.start()) != replacedMatchers.end()) {
+                    conditionsToUpdate[conditionIdx] = UPDATE_REPLACE;
+                    return true;
+                }
+            }
+            if (simplePredicate.has_stop()) {
+                if (replacedMatchers.find(simplePredicate.stop()) != replacedMatchers.end()) {
+                    conditionsToUpdate[conditionIdx] = UPDATE_REPLACE;
+                    return true;
+                }
+            }
+            if (simplePredicate.has_stop_all()) {
+                if (replacedMatchers.find(simplePredicate.stop_all()) != replacedMatchers.end()) {
+                    conditionsToUpdate[conditionIdx] = UPDATE_REPLACE;
+                    return true;
+                }
+            }
+            conditionsToUpdate[conditionIdx] = UPDATE_PRESERVE;
+            return true;
+        }
+        case Predicate::ContentsCase::kCombination: {
+            // Need to recurse on the children to see if any of the child predicates changed.
+            cycleTracker[conditionIdx] = true;
+            UpdateStatus status = UPDATE_PRESERVE;
+            for (const int64_t childPredicateId : predicate.combination().predicate()) {
+                const auto& childIt = newConditionTrackerMap.find(childPredicateId);
+                if (childIt == newConditionTrackerMap.end()) {
+                    ALOGW("Predicate %lld not found in the config", (long long)childPredicateId);
+                    return false;
+                }
+                const int childIdx = childIt->second;
+                if (cycleTracker[childIdx]) {
+                    ALOGE("Cycle detected in predicate config");
+                    return false;
+                }
+                if (!determineConditionUpdateStatus(config, childIdx, oldConditionTrackerMap,
+                                                    oldConditionTrackers, newConditionTrackerMap,
+                                                    replacedMatchers, conditionsToUpdate,
+                                                    cycleTracker)) {
+                    return false;
+                }
+
+                if (conditionsToUpdate[childIdx] == UPDATE_REPLACE) {
+                    status = UPDATE_REPLACE;
+                    break;
+                }
+            }
+            conditionsToUpdate[conditionIdx] = status;
+            cycleTracker[conditionIdx] = false;
+            return true;
+        }
+        default: {
+            ALOGE("Predicate \"%lld\" malformed", (long long)id);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
+                      const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                      const set<int64_t>& replacedMatchers,
+                      const unordered_map<int64_t, int>& oldConditionTrackerMap,
+                      const vector<sp<ConditionTracker>>& oldConditionTrackers,
+                      unordered_map<int64_t, int>& newConditionTrackerMap,
+                      vector<sp<ConditionTracker>>& newConditionTrackers,
+                      unordered_map<int, vector<int>>& trackerToConditionMap,
+                      vector<ConditionState>& conditionCache, set<int64_t>& replacedConditions) {
+    vector<Predicate> conditionProtos;
+    const int conditionTrackerCount = config.predicate_size();
+    conditionProtos.reserve(conditionTrackerCount);
+    newConditionTrackers.reserve(conditionTrackerCount);
+    conditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated);
+
+    for (int i = 0; i < conditionTrackerCount; i++) {
+        const Predicate& condition = config.predicate(i);
+        if (newConditionTrackerMap.find(condition.id()) != newConditionTrackerMap.end()) {
+            ALOGE("Duplicate Predicate found!");
+            return false;
+        }
+        newConditionTrackerMap[condition.id()] = i;
+        conditionProtos.push_back(condition);
+    }
+
+    vector<UpdateStatus> conditionsToUpdate(conditionTrackerCount, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(conditionTrackerCount, false);
+    for (int i = 0; i < conditionTrackerCount; i++) {
+        if (!determineConditionUpdateStatus(config, i, oldConditionTrackerMap, oldConditionTrackers,
+                                            newConditionTrackerMap, replacedMatchers,
+                                            conditionsToUpdate, cycleTracker)) {
+            return false;
+        }
+    }
+
+    // Update status has been determined for all conditions. Now perform the update.
+    set<int> preservedConditions;
+    for (int i = 0; i < conditionTrackerCount; i++) {
+        const Predicate& predicate = config.predicate(i);
+        const int64_t id = predicate.id();
+        switch (conditionsToUpdate[i]) {
+            case UPDATE_PRESERVE: {
+                preservedConditions.insert(i);
+                const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id);
+                if (oldConditionTrackerIt == oldConditionTrackerMap.end()) {
+                    ALOGE("Could not find Predicate %lld in the previous config, but expected it "
+                          "to be there",
+                          (long long)id);
+                    return false;
+                }
+                const int oldIndex = oldConditionTrackerIt->second;
+                newConditionTrackers.push_back(oldConditionTrackers[oldIndex]);
+                break;
+            }
+            case UPDATE_REPLACE:
+                replacedConditions.insert(id);
+                [[fallthrough]];  // Intentionally fallthrough to create the new condition tracker.
+            case UPDATE_NEW: {
+                sp<ConditionTracker> tracker =
+                        createConditionTracker(key, predicate, i, atomMatchingTrackerMap);
+                if (tracker == nullptr) {
+                    return false;
+                }
+                newConditionTrackers.push_back(tracker);
+                break;
+            }
+            default: {
+                ALOGE("Condition \"%lld\" update state is unknown. This should never happen",
+                      (long long)id);
+                return false;
+            }
+        }
+    }
+
+    // Update indices of preserved predicates.
+    for (const int conditionIndex : preservedConditions) {
+        if (!newConditionTrackers[conditionIndex]->onConfigUpdated(
+                    conditionProtos, conditionIndex, newConditionTrackers, atomMatchingTrackerMap,
+                    newConditionTrackerMap)) {
+            ALOGE("Failed to update condition %lld",
+                  (long long)newConditionTrackers[conditionIndex]->getConditionId());
+            return false;
+        }
+    }
+
+    std::fill(cycleTracker.begin(), cycleTracker.end(), false);
+    for (int conditionIndex = 0; conditionIndex < conditionTrackerCount; conditionIndex++) {
+        const sp<ConditionTracker>& conditionTracker = newConditionTrackers[conditionIndex];
+        // Calling init on preserved conditions is OK. It is needed to fill the condition cache.
+        if (!conditionTracker->init(conditionProtos, newConditionTrackers, newConditionTrackerMap,
+                                    cycleTracker, conditionCache)) {
+            return false;
+        }
+        for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) {
+            vector<int>& conditionList = trackerToConditionMap[trackerIndex];
+            conditionList.push_back(conditionIndex);
+        }
+    }
+    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<LogMatchingTracker>>& oldAtomMatchers,
-                        const unordered_map<int64_t, int>& oldLogTrackerMap, set<int>& allTagIds,
-                        vector<sp<LogMatchingTracker>>& newAtomMatchers,
-                        unordered_map<int64_t, int>& newLogTrackerMap) {
-    if (!updateLogTrackers(config, uidMap, oldLogTrackerMap, oldAtomMatchers, allTagIds,
-                           newLogTrackerMap, newAtomMatchers)) {
-        ALOGE("updateLogMatchingTrackers failed");
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const vector<sp<ConditionTracker>>& oldConditionTrackers,
+                        const unordered_map<int64_t, int>& oldConditionTrackerMap,
+                        set<int>& allTagIds,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        vector<sp<ConditionTracker>>& newConditionTrackers,
+                        unordered_map<int64_t, int>& newConditionTrackerMap,
+                        unordered_map<int, vector<int>>& trackerToConditionMap) {
+    set<int64_t> replacedMatchers;
+    set<int64_t> replacedConditions;
+    vector<ConditionState> conditionCache;
+
+    if (!updateAtomMatchingTrackers(config, uidMap, oldAtomMatchingTrackerMap,
+                                    oldAtomMatchingTrackers, allTagIds, newAtomMatchingTrackerMap,
+                                    newAtomMatchingTrackers, replacedMatchers)) {
+        ALOGE("updateAtomMatchingTrackers failed");
         return false;
     }
+    VLOG("updateAtomMatchingTrackers succeeded");
+
+    if (!updateConditions(key, config, newAtomMatchingTrackerMap, replacedMatchers,
+                          oldConditionTrackerMap, oldConditionTrackers, newConditionTrackerMap,
+                          newConditionTrackers, trackerToConditionMap, conditionCache,
+                          replacedConditions)) {
+        ALOGE("updateConditions failed");
+        return false;
+    }
+    VLOG("updateConditions succeeded");
 
     return true;
 }
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // 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
index 951ab03..7ba684a 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -19,8 +19,9 @@
 #include <vector>
 
 #include "anomaly/AlarmMonitor.h"
+#include "condition/ConditionTracker.h"
 #include "external/StatsPullerManager.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 
 namespace android {
 namespace os {
@@ -31,45 +32,97 @@
 // 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.
+// REPLACE means we should create a new one because the existing one changed
+// NEW means we should create a new one because one does not currently exist.
 enum UpdateStatus {
     UPDATE_UNKNOWN = 0,
     UPDATE_PRESERVE = 1,
     UPDATE_REPLACE = 2,
+    UPDATE_NEW = 3,
 };
 
 // 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
-// [newLogTrackerMap]: matcher id to index mapping in the input StatsdConfig
-// [oldLogTrackerMap]: matcher id to index mapping in the existing MetricsManager
-// [oldAtomMatchers]: stores the existing LogMatchingTrackers
+// [oldAtomMatchingTrackerMap]: matcher id to index mapping in the existing MetricsManager
+// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers
+// [newAtomMatchingTrackerMap]: matcher id to index mapping in the input StatsdConfig
 // 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>& oldLogTrackerMap,
-                                  const vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                                  const unordered_map<int64_t, int>& newLogTrackerMap,
-                                  vector<UpdateStatus>& matchersToUpdate,
-                                  vector<bool>& cycleTracker);
+// Returns whether the function was successful or not.
+bool determineMatcherUpdateStatus(
+        const StatsdConfig& config, const int matcherIdx,
+        const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+        const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+        const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+        std::vector<UpdateStatus>& matchersToUpdate, std::vector<bool>& cycleTracker);
 
-// Updates the LogMatchingTrackers.
+// Updates the AtomMatchingTrackers.
 // input:
 // [config]: the input StatsdConfig
-// [oldLogTrackerMap]: existing matcher id to index mapping
-// [oldAtomMatchers]: stores the existing LogMatchingTrackers
+// [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.
-// [newLogTrackerMap]: new matcher id to index mapping
-// [newAtomMatchers]: stores the new LogMatchingTrackers
-bool updateLogTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
-                       const unordered_map<int64_t, int>& oldLogTrackerMap,
-                       const vector<sp<LogMatchingTracker>>& oldAtomMatchers, set<int>& allTagIds,
-                       unordered_map<int64_t, int>& newLogTrackerMap,
-                       vector<sp<LogMatchingTracker>>& newAtomMatchers);
+// [newAtomMatchingTrackerMap]: new matcher id to index mapping
+// [newAtomMatchingTrackers]: stores the new AtomMatchingTrackers
+// [replacedMatchers]: set of matcher ids that changed and have been replaced
+bool updateAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                                const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                                const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                                std::set<int>& allTagIds,
+                                std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                                std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                                std::set<int64_t>& replacedMatchers);
+
+// Recursive function to determine if a condition needs to be updated.
+// input:
+// [config]: the input StatsdConfig
+// [conditionIdx]: the index of the current condition to be updated
+// [oldConditionTrackerMap]: condition id to index mapping in the existing MetricsManager
+// [oldConditionTrackers]: stores the existing ConditionTrackers
+// [newConditionTrackerMap]: condition id to index mapping in the input StatsdConfig
+// [replacedMatchers]: set of replaced matcher ids. conditions using these matchers must be replaced
+// output:
+// [conditionsToUpdate]: vector of the update status of each condition. The conditionIdx index will
+//                       be updated from UPDATE_UNKNOWN after this call.
+// [cycleTracker]: intermediate param used during recursion.
+// Returns whether the function was successful or not.
+bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx,
+                                    const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
+                                    const std::vector<sp<ConditionTracker>>& oldConditionTrackers,
+                                    const std::unordered_map<int64_t, int>& newConditionTrackerMap,
+                                    const std::set<int64_t>& replacedMatchers,
+                                    std::vector<UpdateStatus>& conditionsToUpdate,
+                                    std::vector<bool>& cycleTracker);
+
+// Updates ConditionTrackers
+// input:
+// [config]: the input config
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
+// [replacedMatchers]: ids of replaced matchers. conditions depending on these must also be replaced
+// [oldConditionTrackerMap]: existing matcher id to index mapping
+// [oldConditionTrackers]: stores the existing ConditionTrackers
+// output:
+// [newConditionTrackerMap]: new condition id to index mapping
+// [newConditionTrackers]: stores the sp to all the ConditionTrackers
+// [trackerToConditionMap]: contains the mapping from the index of an atom matcher
+//                          to indices of condition trackers that use the matcher
+// [conditionCache]: stores the current conditions for each ConditionTracker
+// [replacedConditions]: set of matcher ids that have changed and have been replaced
+bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
+                      const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                      const std::set<int64_t>& replacedMatchers,
+                      const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
+                      const std::vector<sp<ConditionTracker>>& oldConditionTrackers,
+                      std::unordered_map<int64_t, int>& newConditionTrackerMap,
+                      std::vector<sp<ConditionTracker>>& newConditionTrackers,
+                      std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                      std::vector<ConditionState>& conditionCache,
+                      std::set<int64_t>& replacedConditions);
 
 // Updates the existing MetricsManager from a new StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -78,12 +131,17 @@
                         const sp<AlarmMonitor>& anomalyAlarmMonitor,
                         const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                         const int64_t currentTimeNs,
-                        const std::vector<sp<LogMatchingTracker>>& oldAtomMatchers,
-                        const unordered_map<int64_t, int>& oldLogTrackerMap,
+                        const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const std::vector<sp<ConditionTracker>>& oldConditionTrackers,
+                        const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
                         std::set<int>& allTagIds,
-                        std::vector<sp<LogMatchingTracker>>& newAtomMatchers,
-                        unordered_map<int64_t, int>& newLogTrackerMap);
+                        std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        std::vector<sp<ConditionTracker>>& newConditionTrackers,
+                        std::unordered_map<int64_t, int>& newConditionTrackerMap,
+                        std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 9d3943f..2e3e434 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -26,9 +26,9 @@
 #include "condition/SimpleConditionTracker.h"
 #include "external/StatsPullerManager.h"
 #include "hash.h"
-#include "matchers/CombinationLogMatchingTracker.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"
@@ -62,8 +62,8 @@
 
 }  // namespace
 
-sp<LogMatchingTracker> createLogTracker(const AtomMatcher& logMatcher, const int index,
-                                        const sp<UidMap>& uidMap) {
+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());
@@ -72,30 +72,51 @@
     uint64_t protoHash = Hash64(serializedMatcher);
     switch (logMatcher.contents_case()) {
         case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
-            return new SimpleLogMatchingTracker(logMatcher.id(), index, protoHash,
-                                                logMatcher.simple_atom_matcher(), uidMap);
-            break;
+            return new SimpleAtomMatchingTracker(logMatcher.id(), index, protoHash,
+                                                 logMatcher.simple_atom_matcher(), uidMap);
         case AtomMatcher::ContentsCase::kCombination:
-            return new CombinationLogMatchingTracker(logMatcher.id(), index, protoHash);
-            break;
+            return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash);
         default:
             ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
             return nullptr;
     }
 }
 
-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<ConditionTracker> createConditionTracker(
+        const ConfigKey& key, const Predicate& predicate, const int index,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
+    string serializedPredicate;
+    if (!predicate.SerializeToString(&serializedPredicate)) {
+        ALOGE("Unable to serialize predicate %lld", (long long)predicate.id());
+        return nullptr;
+    }
+    uint64_t protoHash = Hash64(serializedPredicate);
+    switch (predicate.contents_case()) {
+        case Predicate::ContentsCase::kSimplePredicate: {
+            return new SimpleConditionTracker(key, predicate.id(), protoHash, index,
+                                              predicate.simple_predicate(), atomMatchingTrackerMap);
+        }
+        case Predicate::ContentsCase::kCombination: {
+            return new CombinationConditionTracker(predicate.id(), index, protoHash);
+        }
+        default:
+            ALOGE("Predicate \"%lld\" malformed", (long long)predicate.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);
@@ -107,17 +128,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);
@@ -207,7 +228,7 @@
 bool handleMetricActivation(
         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,
@@ -223,8 +244,8 @@
     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;
         }
@@ -240,8 +261,8 @@
         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;
             }
@@ -255,33 +276,34 @@
     return true;
 }
 
-bool initLogTrackers(const StatsdConfig& config, const sp<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();
-        sp<LogMatchingTracker> tracker = createLogTracker(logMatcher, index, uidMap);
+        sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(logMatcher, i, uidMap);
         if (tracker == nullptr) {
             return false;
         }
-        allAtomMatchers.push_back(tracker);
-        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()] = i;
         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.
@@ -292,7 +314,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,
@@ -301,32 +323,21 @@
     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);
-        int index = allConditionTrackers.size();
-        switch (condition.contents_case()) {
-            case Predicate::ContentsCase::kSimplePredicate: {
-                allConditionTrackers.push_back(new SimpleConditionTracker(
-                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
-                break;
-            }
-            case Predicate::ContentsCase::kCombination: {
-                allConditionTrackers.push_back(
-                        new CombinationConditionTracker(condition.id(), index));
-                break;
-            }
-            default:
-                ALOGE("Predicate \"%lld\" malformed", (long long)condition.id());
-                return false;
+        sp<ConditionTracker> tracker =
+                createConditionTracker(key, condition, i, atomMatchingTrackerMap);
+        if (tracker == nullptr) {
+            return false;
         }
+        allConditionTrackers.push_back(tracker);
         if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) {
             ALOGE("Duplicate Predicate found!");
             return false;
         }
-        conditionTrackerMap[condition.id()] = index;
+        conditionTrackerMap[condition.id()] = i;
         conditionConfigs.push_back(condition);
     }
 
@@ -337,7 +348,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);
         }
@@ -365,9 +376,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,
@@ -380,7 +391,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();
@@ -411,9 +422,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;
         }
 
@@ -448,7 +460,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;
@@ -485,24 +497,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;
         }
 
@@ -554,7 +569,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;
@@ -578,8 +593,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;
         }
 
@@ -601,7 +617,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;
@@ -634,13 +650,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;
@@ -689,7 +706,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;
@@ -725,13 +742,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;
@@ -750,12 +768,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());
         }
 
@@ -783,7 +802,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;
@@ -921,9 +940,10 @@
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                       const int64_t currentTimeNs, set<int>& allTagIds,
-                      vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                      unordered_map<int64_t, int>& logTrackerMap,
+                      vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
+                      unordered_map<int64_t, int>& conditionTrackerMap,
                       vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       vector<sp<AlarmTracker>>& allPeriodicAlarmTrackers,
@@ -934,20 +954,20 @@
                       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> 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;
     }
@@ -956,12 +976,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;
     }
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index ed9951f..6eabcf4 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -23,7 +23,7 @@
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
 #include "external/StatsPullerManager.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
 
 namespace android {
@@ -33,14 +33,25 @@
 // Helper functions for creating individual config components from StatsdConfig.
 // Should only be called from metrics_manager_util and config_update_utils.
 
-// Create a LogMatchingTracker.
+// Create a AtomMatchingTracker.
 // input:
 // [logMatcher]: the input AtomMatcher from the StatsdConfig
 // [index]: the index of the matcher
 // output:
-// new LogMatchingTracker, or null if the tracker is unable to be created
-sp<LogMatchingTracker> createLogTracker(const AtomMatcher& logMatcher, const int index,
-                                        const sp<UidMap>& uidMap);
+// 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);
+
+// Create a ConditionTracker.
+// input:
+// [predicate]: the input Predicate from the StatsdConfig
+// [index]: the index of the condition tracker
+// [atomMatchingTrackerMap]: map of atom matcher id to its index in allAtomMatchingTrackers
+// output:
+// new ConditionTracker, or null if the tracker is unable to be created
+sp<ConditionTracker> createConditionTracker(
+        const ConfigKey& key, const Predicate& predicate, const int index,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap);
 
 // Helper functions for MetricsManager to initialize from StatsdConfig.
 // *Note*: only initStatsdConfig() should be called from outside.
@@ -48,24 +59,24 @@
 // 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 sp<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
@@ -73,11 +84,10 @@
 //                        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,
-                    std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
                     std::vector<ConditionState>& initialConditionCache);
 
 // Initialize State maps using State protos in the config. These maps will
@@ -96,7 +106,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
@@ -109,10 +119,9 @@
 bool initMetrics(
         const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-        const std::unordered_map<int64_t, int>& logTrackerMap,
+        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,
@@ -132,9 +141,10 @@
                       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::unordered_map<int64_t, int>& logTrackerMap,
+                      std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                      std::unordered_map<int64_t, int>& conditionTrackerMap,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       vector<sp<AlarmTracker>>& allPeriodicAlarmTrackers,
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/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 8dd6083..2dd774e 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -23,7 +23,7 @@
 #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"
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/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 07b5311b..8998b5f 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -39,6 +39,7 @@
 const int ATTRIBUTION_NODE_FIELD_ID = 1;
 const int ATTRIBUTION_UID_FIELD_ID = 1;
 const int TAG_ID = 1;
+const uint64_t protoHash = 0x123456789;
 
 SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
                                          bool outputSlicedUid, Position position) {
@@ -123,7 +124,7 @@
     trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
     trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash,
                                             0 /*tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
 
@@ -177,7 +178,7 @@
     trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
     trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash,
                                             0 /*tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
 
@@ -231,8 +232,9 @@
     trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
     trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/,
-                                            simplePredicate, trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash,
+                                            0 /*tracker index*/, simplePredicate,
+                                            trackerNameIndexMap);
     EXPECT_FALSE(conditionTracker.isSliced());
 
     // This event is not accessed in this test besides dimensions which is why this is okay.
@@ -317,7 +319,7 @@
     trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
     trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash,
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
     EXPECT_FALSE(conditionTracker.isSliced());
@@ -392,7 +394,7 @@
         trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
         trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash,
                                                 0 /*condition tracker index*/, simplePredicate,
                                                 trackerNameIndexMap);
 
@@ -514,7 +516,7 @@
     trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
     trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-    SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+    SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash,
                                             0 /*condition tracker index*/, simplePredicate,
                                             trackerNameIndexMap);
 
@@ -610,7 +612,7 @@
         trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
         trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
 
-        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+        SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash,
                                                 0 /*condition tracker index*/, simplePredicate,
                                                 trackerNameIndexMap);
 
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index d96ff8a..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"
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 5524ebc..8790fe4 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"
@@ -57,7 +57,7 @@
 double epsilon = 0.001;
 
 static void assertPastBucketValuesSingleKey(
-        const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets,
+        const std::unordered_map<MetricDimensionKey, std::vector<PastValueBucket>>& mPastBuckets,
         const std::initializer_list<int>& expectedValuesList,
         const std::initializer_list<int64_t>& expectedDurationNsList,
         const std::initializer_list<int64_t>& expectedStartTimeNsList,
@@ -79,7 +79,7 @@
     ASSERT_EQ(1, mPastBuckets.size());
     ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size());
 
-    const vector<ValueBucket>& buckets = mPastBuckets.begin()->second;
+    const vector<PastValueBucket>& buckets = mPastBuckets.begin()->second;
     for (int i = 0; i < expectedValues.size(); i++) {
         EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value)
                 << "Values differ at index " << i;
@@ -288,8 +288,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -304,8 +305,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(23, curBaseInfo.base.long_value);
@@ -322,8 +323,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
@@ -426,8 +427,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -455,8 +457,8 @@
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     // the base was reset
     EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -489,8 +491,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -502,8 +505,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -516,8 +519,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -549,8 +552,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(11, curBaseInfo.base.long_value);
@@ -562,8 +566,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -573,8 +577,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -624,8 +628,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:0 start:100
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
@@ -641,8 +646,8 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(110, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -654,8 +659,8 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -879,8 +884,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -888,7 +894,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(30, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -925,8 +931,8 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
@@ -935,7 +941,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
@@ -946,7 +952,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1089,8 +1095,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     // startUpdated:true sum:0 start:11
     EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -1104,8 +1111,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // tartUpdated:false sum:12
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(23, curBaseInfo.base.long_value);
@@ -1121,8 +1128,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:12
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
@@ -1180,8 +1187,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1189,8 +1197,8 @@
 
     // pull on bucket boundary come late, condition change happens before it
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -1203,8 +1211,8 @@
 
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -1252,8 +1260,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // startUpdated:false sum:0 start:100
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
@@ -1265,8 +1274,8 @@
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 
@@ -1274,8 +1283,8 @@
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25);
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(130, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1286,8 +1295,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50);
 
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1327,7 +1336,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1335,7 +1344,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1364,7 +1373,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1374,7 +1383,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1405,7 +1414,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval;
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(1, curInterval.sampleSize);
@@ -1414,7 +1423,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(25, curInterval.value.long_value);
     EXPECT_EQ(2, curInterval.sampleSize);
 
@@ -1449,7 +1458,7 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
@@ -1457,7 +1466,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(25, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
@@ -1487,8 +1496,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1499,7 +1509,7 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(5, curInterval.value.long_value);
 
@@ -1509,8 +1519,8 @@
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1520,8 +1530,8 @@
     CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1558,12 +1568,13 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+            valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(20, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1572,12 +1583,12 @@
 
     // has one slice
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(5, curInterval.value.long_value);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(2, curInterval.value.long_value);
 
@@ -1587,14 +1598,14 @@
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
 
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(25, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1604,13 +1615,13 @@
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
-    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1];
-    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1];
+    curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1];
+    curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(29, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
@@ -1656,9 +1667,9 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
-    auto& interval1 = iter->second[0];
+    auto& interval1 = iter->second.intervals[0];
     auto iterBase = valueProducer->mCurrentBaseInfo.begin();
-    auto& baseInfo1 = iterBase->second[0];
+    auto& baseInfo1 = iterBase->second.baseInfos[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1692,8 +1703,8 @@
     }
     EXPECT_TRUE(it != iter);
     EXPECT_TRUE(itBase != iterBase);
-    auto& interval2 = it->second[0];
-    auto& baseInfo2 = itBase->second[0];
+    auto& interval2 = it->second.intervals[0];
+    auto& baseInfo2 = itBase->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1732,9 +1743,10 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     const auto& it = valueProducer->mCurrentSlicedBucket.begin();
-    ValueMetricProducer::Interval& interval1 = it->second[0];
+    ValueMetricProducer::Interval& interval1 = it->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo1 =
-            valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1761,9 +1773,10 @@
         }
     }
     EXPECT_TRUE(it2 != it);
-    ValueMetricProducer::Interval& interval2 = it2->second[0];
+    ValueMetricProducer::Interval& interval2 = it2->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo2 =
-            valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1792,14 +1805,16 @@
     // Get new references now that entries have been deleted from the map
     const auto& it3 = valueProducer->mCurrentSlicedBucket.begin();
     const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin());
-    ASSERT_EQ(it3->second.size(), 1);
-    ASSERT_EQ(it4->second.size(), 1);
-    ValueMetricProducer::Interval& interval3 = it3->second[0];
-    ValueMetricProducer::Interval& interval4 = it4->second[0];
+    ASSERT_EQ(it3->second.intervals.size(), 1);
+    ASSERT_EQ(it4->second.intervals.size(), 1);
+    ValueMetricProducer::Interval& interval3 = it3->second.intervals[0];
+    ValueMetricProducer::Interval& interval4 = it4->second.intervals[0];
     ValueMetricProducer::BaseInfo& baseInfo3 =
-            valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
     ValueMetricProducer::BaseInfo& baseInfo4 =
-            valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0];
+            valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())
+                    ->second.baseInfos[0];
 
     EXPECT_EQ(true, baseInfo3.hasBase);
     EXPECT_EQ(5, baseInfo3.base.long_value);
@@ -1837,9 +1852,9 @@
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto iter = valueProducer->mCurrentSlicedBucket.begin();
-    auto& interval1 = iter->second[0];
+    auto& interval1 = iter->second.intervals[0];
     auto iterBase = valueProducer->mCurrentBaseInfo.begin();
-    auto& baseInfo1 = iterBase->second[0];
+    auto& baseInfo1 = iterBase->second.baseInfos[0];
     EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo1.hasBase);
     EXPECT_EQ(3, baseInfo1.base.long_value);
@@ -1875,8 +1890,8 @@
     }
     EXPECT_TRUE(it != iter);
     EXPECT_TRUE(itBase != iterBase);
-    auto interval2 = it->second[0];
-    auto baseInfo2 = itBase->second[0];
+    auto interval2 = it->second.intervals[0];
+    auto baseInfo2 = itBase->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
@@ -1889,8 +1904,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs);
     // Only one interval left. One was trimmed.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(5, baseInfo2.base.long_value);
@@ -1903,8 +1918,8 @@
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs);
 
-    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(14, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
@@ -1943,8 +1958,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo& curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -1980,8 +1996,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo& curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2030,8 +2047,9 @@
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 1);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2103,8 +2121,9 @@
     valueProducer->mHasGlobalBase = true;
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(100, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2156,8 +2175,9 @@
     // Contains base from last pull which was successful.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2294,8 +2314,9 @@
     // Contains base from last pull which was successful.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2373,8 +2394,9 @@
     // Last pull failed so base has been reset.
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2460,8 +2482,9 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
@@ -2469,8 +2492,8 @@
     // Empty pull.
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
     EXPECT_EQ(false, valueProducer->mHasGlobalBase);
@@ -2513,8 +2536,9 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 12);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval& curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
@@ -2524,8 +2548,8 @@
     allData.clear();
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     // Data is empty, base should be reset.
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(5, curBaseInfo.base.long_value);
@@ -2570,14 +2594,14 @@
     ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
     auto iterator = valueProducer->mCurrentSlicedBucket.begin();
     auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin();
-    EXPECT_EQ(true, baseInfoIter->second[0].hasBase);
-    EXPECT_EQ(2, baseInfoIter->second[0].base.long_value);
-    EXPECT_EQ(false, iterator->second[0].hasValue);
+    EXPECT_EQ(true, baseInfoIter->second.baseInfos[0].hasBase);
+    EXPECT_EQ(2, baseInfoIter->second.baseInfos[0].base.long_value);
+    EXPECT_EQ(false, iterator->second.intervals[0].hasValue);
     iterator++;
     baseInfoIter++;
-    EXPECT_EQ(false, baseInfoIter->second[0].hasBase);
-    EXPECT_EQ(1, baseInfoIter->second[0].base.long_value);
-    EXPECT_EQ(false, iterator->second[0].hasValue);
+    EXPECT_EQ(false, baseInfoIter->second.baseInfos[0].hasBase);
+    EXPECT_EQ(1, baseInfoIter->second.baseInfos[0].base.long_value);
+    EXPECT_EQ(false, iterator->second.intervals[0].hasValue);
 
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 }
@@ -2676,8 +2700,8 @@
 
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10);
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(5, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
@@ -2811,8 +2835,8 @@
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 12);
 
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(2, curInterval.value.long_value);
 
@@ -3045,8 +3069,9 @@
     // has one slice
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(20, curInterval.value.long_value);
@@ -3058,8 +3083,8 @@
 
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
-    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+    curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -3091,8 +3116,9 @@
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8},
                                     {bucketStartTimeNs}, {bucket2StartTimeNs});
     ValueMetricProducer::Interval curInterval =
-            valueProducer->mCurrentSlicedBucket.begin()->second[0];
-    ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
+            valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
+    ValueMetricProducer::BaseInfo curBaseInfo =
+            valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
     EXPECT_EQ(false, curInterval.hasValue);
 }
@@ -3984,18 +4010,18 @@
     // Base for dimension key {}
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after screen state change kStateUnknown->ON.
     auto screenEvent = CreateScreenStateChangedEvent(
@@ -4005,19 +4031,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change ON->OFF.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
@@ -4027,26 +4053,26 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(9, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, ON}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change OFF->ON.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
@@ -4056,35 +4082,35 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(21, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, OFF}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(12, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(12, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, ON}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
@@ -4195,18 +4221,18 @@
     // Base for dimension key {}
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, {kStateUnknown}}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after screen state change kStateUnknown->ON.
     auto screenEvent = CreateScreenStateChangedEvent(
@@ -4216,19 +4242,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.long_value);
+              itBase->second.currentState.getValues()[0].mValue.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change ON->VR.
     // Both ON and VR are in the same state group, so the base should not change.
@@ -4239,19 +4265,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change VR->ON.
     // Both ON and VR are in the same state group, so the base should not change.
@@ -4262,19 +4288,19 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, kStateUnknown}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Bucket status after screen state change VR->OFF.
     screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
@@ -4284,27 +4310,27 @@
     // Base for dimension key {}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(21, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(screenOffGroup.group_id(),
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{}, ON GROUP}
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(screenOnGroup.group_id(),
               it->first.getStateValuesKey().getValues()[0].mValue.long_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(16, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(16, it->second.intervals[0].value.long_value);
     // Value for dimension, state key {{}, kStateUnknown}
     it++;
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
@@ -4447,35 +4473,35 @@
     // Base for dimension key {uid 1}.
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{uid 1}, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(7, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for dimension, state key {{uid 2}, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change kStateUnknown -> Foreground.
     auto uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4485,36 +4511,36 @@
     // Base for dimension key {uid 1}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(6, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(7, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 2 process state change kStateUnknown -> Background.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4524,36 +4550,36 @@
     // Base for dimension key {uid 1}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(6, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Base for dimension key {uid 2}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(9, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Pull at end of first bucket.
     vector<shared_ptr<LogEvent>> allData;
@@ -4570,36 +4596,36 @@
     it = valueProducer->mCurrentSlicedBucket.begin();
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(10, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(10, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 1, FOREGROUND}
     it++;
@@ -4608,7 +4634,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 2, kStateUnknown}
     it++;
@@ -4617,7 +4643,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change from Foreground -> Background.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4630,35 +4656,35 @@
     // Base for dimension key {uid 2}.
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}.
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(13, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(13, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
     // Value for key {uid 1, FOREGROUND}
     it++;
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4666,8 +4692,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
     // Value for key {uid 2, kStateUnknown}
     it++;
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4675,7 +4701,7 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after uid 1 process state change Background->Foreground.
     uidProcessEvent = CreateUidProcessStateChangedEvent(
@@ -4687,36 +4713,36 @@
     // Base for dimension key {uid 2}
     it = valueProducer->mCurrentSlicedBucket.begin();
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(15, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 2, BACKGROUND}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Base for dimension key {uid 1}
     it++;
     itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(17, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(17, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {uid 1, kStateUnknown}
     ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
     EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Value for key {uid 1, BACKGROUND}
     it++;
@@ -4725,8 +4751,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
 
     // Value for key {uid 1, FOREGROUND}
     it++;
@@ -4735,8 +4761,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(3, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(3, it->second.intervals[0].value.long_value);
 
     // Value for key {uid 2, kStateUnknown}
     it++;
@@ -4857,23 +4883,23 @@
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC);
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
-    std::unordered_map<HashableDimensionKey, std::vector<ValueMetricProducer::BaseInfo>>::iterator
+    std::unordered_map<HashableDimensionKey, ValueMetricProducer::DimensionsInWhatInfo>::iterator
             itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(3, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::ON,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, -1}
     ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
-    std::unordered_map<MetricDimensionKey, std::vector<ValueMetricProducer::Interval>>::iterator
-            it = valueProducer->mCurrentSlicedBucket.begin();
+    std::unordered_map<MetricDimensionKey, ValueMetricProducer::CurrentValueBucket>::iterator it =
+            valueProducer->mCurrentSlicedBucket.begin();
     EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_FALSE(it->second[0].hasValue);
+    EXPECT_FALSE(it->second.intervals[0].hasValue);
 
     // Bucket status after battery saver mode OFF event.
     unique_ptr<LogEvent> batterySaverOffEvent =
@@ -4882,12 +4908,12 @@
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(5, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, ON}
     ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
     it = valueProducer->mCurrentSlicedBucket.begin();
@@ -4895,8 +4921,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::ON,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(2, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(2, it->second.intervals[0].value.long_value);
 
     // Pull at end of first bucket.
     vector<shared_ptr<LogEvent>> allData;
@@ -4909,23 +4935,23 @@
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_TRUE(itBase->second[0].hasBase);
-    EXPECT_EQ(11, itBase->second[0].base.long_value);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
 
     // Bucket 2 status after condition change to false.
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC);
     // Base for dimension key {}
     ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
     itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY);
-    EXPECT_FALSE(itBase->second[0].hasBase);
-    EXPECT_TRUE(itBase->second[0].hasCurrentState);
-    ASSERT_EQ(1, itBase->second[0].currentState.getValues().size());
+    EXPECT_FALSE(itBase->second.baseInfos[0].hasBase);
+    EXPECT_TRUE(itBase->second.hasCurrentState);
+    ASSERT_EQ(1, itBase->second.currentState.getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
-              itBase->second[0].currentState.getValues()[0].mValue.int_value);
+              itBase->second.currentState.getValues()[0].mValue.int_value);
     // Value for key {{}, OFF}
     ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
     it = valueProducer->mCurrentSlicedBucket.begin();
@@ -4933,8 +4959,8 @@
     ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
     EXPECT_EQ(BatterySaverModeStateChanged::OFF,
               it->first.getStateValuesKey().getValues()[0].mValue.int_value);
-    EXPECT_TRUE(it->second[0].hasValue);
-    EXPECT_EQ(4, it->second[0].value.long_value);
+    EXPECT_TRUE(it->second.intervals[0].hasValue);
+    EXPECT_EQ(4, it->second.intervals[0].value.long_value);
 
     // Start dump report and check output.
     ProtoOutputStream output;
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
index 6b50fe5..890884b 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -14,6 +14,7 @@
 
 #include "src/metrics/parsing_utils/config_update_utils.h"
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <private/android_filesystem_config.h>
 #include <stdio.h>
@@ -23,6 +24,9 @@
 #include <vector>
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/condition/CombinationConditionTracker.h"
+#include "src/condition/SimpleConditionTracker.h"
+#include "src/matchers/CombinationAtomMatchingTracker.h"
 #include "src/metrics/parsing_utils/metrics_manager_util.h"
 #include "tests/statsd_test_util.h"
 
@@ -49,9 +53,10 @@
 sp<AlarmMonitor> anomalyAlarmMonitor;
 sp<AlarmMonitor> periodicAlarmMonitor;
 set<int> allTagIds;
-vector<sp<LogMatchingTracker>> oldAtomMatchers;
-unordered_map<int64_t, int> oldLogTrackerMap;
+vector<sp<AtomMatchingTracker>> oldAtomMatchingTrackers;
+unordered_map<int64_t, int> oldAtomMatchingTrackerMap;
 vector<sp<ConditionTracker>> oldConditionTrackers;
+unordered_map<int64_t, int> oldConditionTrackerMap;
 vector<sp<MetricProducer>> oldMetricProducers;
 std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
 std::vector<sp<AlarmTracker>> oldAlarmTrackers;
@@ -71,9 +76,10 @@
 
     void SetUp() override {
         allTagIds.clear();
-        oldAtomMatchers.clear();
-        oldLogTrackerMap.clear();
+        oldAtomMatchingTrackers.clear();
+        oldAtomMatchingTrackerMap.clear();
         oldConditionTrackers.clear();
+        oldConditionTrackerMap.clear();
         oldMetricProducers.clear();
         oldAnomalyTrackers.clear();
         oldAlarmTrackers.clear();
@@ -89,13 +95,13 @@
 };
 
 bool initConfig(const StatsdConfig& config) {
-    return initStatsdConfig(key, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                            periodicAlarmMonitor, timeBaseNs, timeBaseNs, allTagIds,
-                            oldAtomMatchers, oldLogTrackerMap, oldConditionTrackers,
-                            oldMetricProducers, oldAnomalyTrackers, oldAlarmTrackers,
-                            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
-                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                            alertTrackerMap, metricsWithActivation, noReportMetricIds);
+    return initStatsdConfig(
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap,
+            oldConditionTrackers, oldConditionTrackerMap, oldMetricProducers, oldAnomalyTrackers,
+            oldAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds);
 }
 
 }  // anonymous namespace
@@ -111,10 +117,11 @@
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(1, false);
-    unordered_map<int64_t, int> newLogTrackerMap;
-    newLogTrackerMap[matcherId] = 0;
-    EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    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);
 }
 
@@ -134,13 +141,38 @@
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(1, false);
-    unordered_map<int64_t, int> newLogTrackerMap;
-    newLogTrackerMap[matcherId] = 0;
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    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, TestSimpleMatcherNew) {
+    StatsdConfig config;
+    AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
+    *config.add_atom_matcher() = matcher;
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig;
+    // Different id, so should be a new matcher.
+    AtomMatcher newMatcher = CreateSimpleAtomMatcher("DIFFERENT_NAME", /*atom=*/10);
+    int64_t matcherId = newMatcher.id();
+    EXPECT_NE(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_NEW);
+}
+
 TEST_F(ConfigUpdateTest, TestCombinationMatcherPreserve) {
     StatsdConfig config;
     AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
@@ -163,20 +195,21 @@
     EXPECT_TRUE(initConfig(config));
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     // Same matchers, different order, all should be preserved.
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    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, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    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);
@@ -207,19 +240,20 @@
     matcher3.mutable_combination()->set_operation(LogicalOperation::AND);
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    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, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    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);
@@ -250,19 +284,20 @@
     matcher2.mutable_simple_atom_matcher()->set_atom_id(12);
 
     StatsdConfig newConfig;
-    unordered_map<int64_t, int> newLogTrackerMap;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     *newConfig.add_atom_matcher() = matcher2;
-    newLogTrackerMap[matcher2Id] = 0;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
     *newConfig.add_atom_matcher() = matcher3;
-    newLogTrackerMap[matcher3Id] = 1;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
     *newConfig.add_atom_matcher() = matcher1;
-    newLogTrackerMap[matcher1Id] = 2;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
     vector<bool> cycleTracker(3, false);
     // Only update the combination.
-    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldLogTrackerMap, oldAtomMatchers,
-                                             newLogTrackerMap, matchersToUpdate, cycleTracker));
+    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);
@@ -330,49 +365,524 @@
     *newConfig.add_atom_matcher() = combination1;
 
     set<int> newTagIds;
-    unordered_map<int64_t, int> newLogTrackerMap;
-    vector<sp<LogMatchingTracker>> newAtomMatchers;
-    EXPECT_TRUE(updateLogTrackers(newConfig, uidMap, oldLogTrackerMap, oldAtomMatchers, newTagIds,
-                                  newLogTrackerMap, newAtomMatchers));
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    set<int64_t> replacedMatchers;
+    EXPECT_TRUE(updateAtomMatchingTrackers(
+            newConfig, uidMap, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers, newTagIds,
+            newAtomMatchingTrackerMap, newAtomMatchingTrackers, replacedMatchers));
 
     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(newLogTrackerMap.size(), 6);
-    EXPECT_EQ(newLogTrackerMap.at(combination3Id), 0);
-    EXPECT_EQ(newLogTrackerMap.at(simple2Id), 1);
-    EXPECT_EQ(newLogTrackerMap.at(combination2Id), 2);
-    EXPECT_EQ(newLogTrackerMap.at(simple1Id), 3);
-    EXPECT_EQ(newLogTrackerMap.at(simple4Id), 4);
-    EXPECT_EQ(newLogTrackerMap.at(combination1Id), 5);
+    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(newAtomMatchers.size(), 6);
+    ASSERT_EQ(newAtomMatchingTrackers.size(), 6);
     // Make sure all atom matchers are initialized:
-    for (const sp<LogMatchingTracker>& tracker : newAtomMatchers) {
+    for (const sp<AtomMatchingTracker>& tracker : newAtomMatchingTrackers) {
         EXPECT_TRUE(tracker->mInitialized);
     }
     // Make sure preserved atom matchers are the same.
-    EXPECT_EQ(oldAtomMatchers[oldLogTrackerMap.at(simple1Id)],
-              newAtomMatchers[newLogTrackerMap.at(simple1Id)]);
-    EXPECT_EQ(oldAtomMatchers[oldLogTrackerMap.at(combination1Id)],
-              newAtomMatchers[newLogTrackerMap.at(combination1Id)]);
+    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(oldAtomMatchers[oldLogTrackerMap.at(simple2Id)],
-              newAtomMatchers[newLogTrackerMap.at(simple2Id)]);
-    EXPECT_NE(oldAtomMatchers[oldLogTrackerMap.at(combination2Id)],
-              newAtomMatchers[newLogTrackerMap.at(combination2Id)]);
+    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. Could do more checks here.
-    EXPECT_EQ(newAtomMatchers[0]->getId(), combination3Id);
-    EXPECT_EQ(newAtomMatchers[1]->getId(), simple2Id);
-    EXPECT_EQ(newAtomMatchers[2]->getId(), combination2Id);
-    EXPECT_EQ(newAtomMatchers[3]->getId(), simple1Id);
-    EXPECT_EQ(newAtomMatchers[4]->getId(), simple4Id);
-    EXPECT_EQ(newAtomMatchers[5]->getId(), combination1Id);
+    // 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());
+
+    // Expect replacedMatchers to have simple2 and combination2
+    ASSERT_EQ(replacedMatchers.size(), 2);
+    EXPECT_NE(replacedMatchers.find(simple2Id), replacedMatchers.end());
+    EXPECT_NE(replacedMatchers.find(combination2Id), replacedMatchers.end());
 }
 
+TEST_F(ConfigUpdateTest, TestSimpleConditionPreserve) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate predicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = predicate;
+
+    // Create an initial config.
+    EXPECT_TRUE(initConfig(config));
+
+    set<int64_t> replacedMatchers;
+    vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(1, false);
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    newConditionTrackerMap[predicate.id()] = 0;
+    EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestSimpleConditionReplace) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate predicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = predicate;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Modify the predicate.
+    config.mutable_predicate(0)->mutable_simple_predicate()->set_count_nesting(true);
+
+    set<int64_t> replacedMatchers;
+    vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(1, false);
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    newConditionTrackerMap[predicate.id()] = 0;
+    EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestSimpleConditionDepsChange) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    int64_t startMatcherId = startMatcher.id();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate predicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = predicate;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Start matcher was replaced.
+    set<int64_t> replacedMatchers;
+    replacedMatchers.insert(startMatcherId);
+
+    vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(1, false);
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    newConditionTrackerMap[predicate.id()] = 0;
+    EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationConditionPreserve) {
+    StatsdConfig config;
+    AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnMatcher;
+    AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffMatcher;
+
+    Predicate simple1 = CreateScreenIsOnPredicate();
+    *config.add_predicate() = simple1;
+    Predicate simple2 = CreateScreenIsOffPredicate();
+    *config.add_predicate() = simple2;
+
+    Predicate combination1;
+    combination1.set_id(StringToId("COMBINATION1"));
+    Predicate_Combination* combinationInternal = combination1.mutable_combination();
+    combinationInternal->set_operation(LogicalOperation::NAND);
+    combinationInternal->add_predicate(simple1.id());
+    combinationInternal->add_predicate(simple2.id());
+    *config.add_predicate() = combination1;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Same predicates, different order
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    *newConfig.add_predicate() = combination1;
+    newConditionTrackerMap[combination1.id()] = 0;
+    *newConfig.add_predicate() = simple2;
+    newConditionTrackerMap[simple2.id()] = 1;
+    *newConfig.add_predicate() = simple1;
+    newConditionTrackerMap[simple1.id()] = 2;
+
+    set<int64_t> replacedMatchers;
+    vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination. It should recurse the two child predicates and preserve all 3.
+    EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE);
+    EXPECT_EQ(conditionsToUpdate[1], UPDATE_PRESERVE);
+    EXPECT_EQ(conditionsToUpdate[2], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationConditionReplace) {
+    StatsdConfig config;
+    AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnMatcher;
+    AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffMatcher;
+
+    Predicate simple1 = CreateScreenIsOnPredicate();
+    *config.add_predicate() = simple1;
+    Predicate simple2 = CreateScreenIsOffPredicate();
+    *config.add_predicate() = simple2;
+
+    Predicate combination1;
+    combination1.set_id(StringToId("COMBINATION1"));
+    Predicate_Combination* combinationInternal = combination1.mutable_combination();
+    combinationInternal->set_operation(LogicalOperation::NAND);
+    combinationInternal->add_predicate(simple1.id());
+    combinationInternal->add_predicate(simple2.id());
+    *config.add_predicate() = combination1;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Changing the logical operation changes the predicate definition, so it should be replaced.
+    combination1.mutable_combination()->set_operation(LogicalOperation::OR);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    *newConfig.add_predicate() = combination1;
+    newConditionTrackerMap[combination1.id()] = 0;
+    *newConfig.add_predicate() = simple2;
+    newConditionTrackerMap[simple2.id()] = 1;
+    *newConfig.add_predicate() = simple1;
+    newConditionTrackerMap[simple1.id()] = 2;
+
+    set<int64_t> replacedMatchers;
+    vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination. The simple conditions should not be evaluated.
+    EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE);
+    EXPECT_EQ(conditionsToUpdate[1], UPDATE_UNKNOWN);
+    EXPECT_EQ(conditionsToUpdate[2], UPDATE_UNKNOWN);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationConditionDepsChange) {
+    StatsdConfig config;
+    AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnMatcher;
+    AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffMatcher;
+
+    Predicate simple1 = CreateScreenIsOnPredicate();
+    *config.add_predicate() = simple1;
+    Predicate simple2 = CreateScreenIsOffPredicate();
+    *config.add_predicate() = simple2;
+
+    Predicate combination1;
+    combination1.set_id(StringToId("COMBINATION1"));
+    Predicate_Combination* combinationInternal = combination1.mutable_combination();
+    combinationInternal->set_operation(LogicalOperation::NAND);
+    combinationInternal->add_predicate(simple1.id());
+    combinationInternal->add_predicate(simple2.id());
+    *config.add_predicate() = combination1;
+
+    EXPECT_TRUE(initConfig(config));
+
+    simple2.mutable_simple_predicate()->set_count_nesting(false);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    *newConfig.add_predicate() = combination1;
+    newConditionTrackerMap[combination1.id()] = 0;
+    *newConfig.add_predicate() = simple2;
+    newConditionTrackerMap[simple2.id()] = 1;
+    *newConfig.add_predicate() = simple1;
+    newConditionTrackerMap[simple1.id()] = 2;
+
+    set<int64_t> replacedMatchers;
+    vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination. Simple2 and combination1 must be evaluated.
+    EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
+                                               oldConditionTrackers, newConditionTrackerMap,
+                                               replacedMatchers, conditionsToUpdate, cycleTracker));
+    EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE);
+    EXPECT_EQ(conditionsToUpdate[1], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateConditions) {
+    StatsdConfig config;
+
+    // Add atom matchers. These are mostly needed for initStatsdConfig
+    AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+    int64_t matcher2Id = matcher2.id();
+    *config.add_atom_matcher() = matcher2;
+
+    AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher();
+    int64_t matcher4Id = matcher4.id();
+    *config.add_atom_matcher() = matcher4;
+
+    AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher();
+    int64_t matcher5Id = matcher5.id();
+    *config.add_atom_matcher() = matcher5;
+
+    AtomMatcher matcher6 = CreateBatterySaverModeStopAtomMatcher();
+    int64_t matcher6Id = matcher6.id();
+    *config.add_atom_matcher() = matcher6;
+
+    // Add the predicates.
+    // Will be preserved.
+    Predicate simple1 = CreateScreenIsOnPredicate();
+    int64_t simple1Id = simple1.id();
+    *config.add_predicate() = simple1;
+
+    // Will be preserved.
+    Predicate simple2 = CreateScheduledJobPredicate();
+    int64_t simple2Id = simple2.id();
+    *config.add_predicate() = simple2;
+
+    // Will be replaced.
+    Predicate simple3 = CreateBatterySaverModePredicate();
+    int64_t simple3Id = simple3.id();
+    *config.add_predicate() = simple3;
+
+    // Will be preserved
+    Predicate combination1;
+    combination1.set_id(StringToId("COMBINATION1"));
+    combination1.mutable_combination()->set_operation(LogicalOperation::AND);
+    combination1.mutable_combination()->add_predicate(simple1Id);
+    combination1.mutable_combination()->add_predicate(simple2Id);
+    int64_t combination1Id = combination1.id();
+    *config.add_predicate() = combination1;
+
+    // Will be replaced since simple3 will be replaced.
+    Predicate combination2;
+    combination2.set_id(StringToId("COMBINATION2"));
+    combination2.mutable_combination()->set_operation(LogicalOperation::OR);
+    combination2.mutable_combination()->add_predicate(simple1Id);
+    combination2.mutable_combination()->add_predicate(simple3Id);
+    int64_t combination2Id = combination2.id();
+    *config.add_predicate() = combination2;
+
+    // Will be removed.
+    Predicate combination3;
+    combination3.set_id(StringToId("COMBINATION3"));
+    combination3.mutable_combination()->set_operation(LogicalOperation::NOT);
+    combination3.mutable_combination()->add_predicate(simple2Id);
+    int64_t combination3Id = combination3.id();
+    *config.add_predicate() = combination3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Mark marcher 5 as replaced. Causes simple3, and therefore combination2 to be replaced.
+    set<int64_t> replacedMatchers;
+    replacedMatchers.insert(matcher6Id);
+
+    // Change the condition of simple1 to true.
+    ASSERT_EQ(oldConditionTrackers[0]->getConditionId(), simple1Id);
+    LogEvent event(/*uid=*/0, /*pid=*/0);  // Empty event is fine since there are no dimensions.
+    // Mark the stop matcher as matched, condition should be false.
+    vector<MatchingState> eventMatcherValues(6, MatchingState::kNotMatched);
+    eventMatcherValues[1] = MatchingState::kMatched;
+    vector<ConditionState> tmpConditionCache(6, ConditionState::kNotEvaluated);
+    vector<bool> conditionChangeCache(6, false);
+    oldConditionTrackers[0]->evaluateCondition(event, eventMatcherValues, oldConditionTrackers,
+                                               tmpConditionCache, conditionChangeCache);
+    EXPECT_EQ(tmpConditionCache[0], ConditionState::kFalse);
+    EXPECT_EQ(conditionChangeCache[0], true);
+
+    // New combination matcher. Should have an initial condition of true since it is NOT(simple1).
+    Predicate combination4;
+    combination4.set_id(StringToId("COMBINATION4"));
+    combination4.mutable_combination()->set_operation(LogicalOperation::NOT);
+    combination4.mutable_combination()->add_predicate(simple1Id);
+    int64_t combination4Id = combination4.id();
+    *config.add_predicate() = combination4;
+
+    // Map the matchers in reverse order to force the indices to change.
+    std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    const int matcher6Index = 0;
+    newAtomMatchingTrackerMap[matcher6Id] = 0;
+    const int matcher5Index = 1;
+    newAtomMatchingTrackerMap[matcher5Id] = 1;
+    const int matcher4Index = 2;
+    newAtomMatchingTrackerMap[matcher4Id] = 2;
+    const int matcher3Index = 3;
+    newAtomMatchingTrackerMap[matcher3Id] = 3;
+    const int matcher2Index = 4;
+    newAtomMatchingTrackerMap[matcher2Id] = 4;
+    const int matcher1Index = 5;
+    newAtomMatchingTrackerMap[matcher1Id] = 5;
+
+    StatsdConfig newConfig;
+    *newConfig.add_predicate() = simple3;
+    const int simple3Index = 0;
+    *newConfig.add_predicate() = combination2;
+    const int combination2Index = 1;
+    *newConfig.add_predicate() = combination4;
+    const int combination4Index = 2;
+    *newConfig.add_predicate() = simple2;
+    const int simple2Index = 3;
+    *newConfig.add_predicate() = combination1;
+    const int combination1Index = 4;
+    *newConfig.add_predicate() = simple1;
+    const int simple1Index = 5;
+
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    vector<sp<ConditionTracker>> newConditionTrackers;
+    unordered_map<int, vector<int>> trackerToConditionMap;
+    std::vector<ConditionState> conditionCache;
+    std::set<int64_t> replacedConditions;
+    EXPECT_TRUE(updateConditions(key, newConfig, newAtomMatchingTrackerMap, replacedMatchers,
+                                 oldConditionTrackerMap, oldConditionTrackers,
+                                 newConditionTrackerMap, newConditionTrackers,
+                                 trackerToConditionMap, conditionCache, replacedConditions));
+
+    unordered_map<int64_t, int> expectedConditionTrackerMap = {
+            {simple1Id, simple1Index},           {simple2Id, simple2Index},
+            {simple3Id, simple3Index},           {combination1Id, combination1Index},
+            {combination2Id, combination2Index}, {combination4Id, combination4Index},
+    };
+    EXPECT_THAT(newConditionTrackerMap, ContainerEq(expectedConditionTrackerMap));
+
+    ASSERT_EQ(newConditionTrackers.size(), 6);
+    // Make sure all conditions are initialized:
+    for (const sp<ConditionTracker>& tracker : newConditionTrackers) {
+        EXPECT_TRUE(tracker->mInitialized);
+    }
+
+    // Make sure preserved conditions are the same.
+    EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple1Id)],
+              newConditionTrackers[newConditionTrackerMap.at(simple1Id)]);
+    EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple2Id)],
+              newConditionTrackers[newConditionTrackerMap.at(simple2Id)]);
+    EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(combination1Id)],
+              newConditionTrackers[newConditionTrackerMap.at(combination1Id)]);
+
+    // Make sure replaced conditions are different and included in replacedConditions.
+    EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(simple3Id)],
+              newConditionTrackers[newConditionTrackerMap.at(simple3Id)]);
+    EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(combination2Id)],
+              newConditionTrackers[newConditionTrackerMap.at(combination2Id)]);
+    EXPECT_THAT(replacedConditions, ContainerEq(set({simple3Id, combination2Id})));
+
+    // Verify the trackerToConditionMap
+    ASSERT_EQ(trackerToConditionMap.size(), 6);
+    const vector<int>& matcher1Conditions = trackerToConditionMap[matcher1Index];
+    EXPECT_THAT(matcher1Conditions, UnorderedElementsAre(simple1Index, combination1Index,
+                                                         combination2Index, combination4Index));
+    const vector<int>& matcher2Conditions = trackerToConditionMap[matcher2Index];
+    EXPECT_THAT(matcher2Conditions, UnorderedElementsAre(simple1Index, combination1Index,
+                                                         combination2Index, combination4Index));
+    const vector<int>& matcher3Conditions = trackerToConditionMap[matcher3Index];
+    EXPECT_THAT(matcher3Conditions, UnorderedElementsAre(simple2Index, combination1Index));
+    const vector<int>& matcher4Conditions = trackerToConditionMap[matcher4Index];
+    EXPECT_THAT(matcher4Conditions, UnorderedElementsAre(simple2Index, combination1Index));
+    const vector<int>& matcher5Conditions = trackerToConditionMap[matcher5Index];
+    EXPECT_THAT(matcher5Conditions, UnorderedElementsAre(simple3Index, combination2Index));
+    const vector<int>& matcher6Conditions = trackerToConditionMap[matcher6Index];
+    EXPECT_THAT(matcher6Conditions, UnorderedElementsAre(simple3Index, combination2Index));
+
+    // Verify the conditionCache. Specifically, simple1 is false and combination4 is true.
+    ASSERT_EQ(conditionCache.size(), 6);
+    EXPECT_EQ(conditionCache[simple1Index], ConditionState::kFalse);
+    EXPECT_EQ(conditionCache[simple2Index], ConditionState::kUnknown);
+    EXPECT_EQ(conditionCache[simple3Index], ConditionState::kUnknown);
+    EXPECT_EQ(conditionCache[combination1Index], ConditionState::kUnknown);
+    EXPECT_EQ(conditionCache[combination2Index], ConditionState::kUnknown);
+    EXPECT_EQ(conditionCache[combination4Index], ConditionState::kTrue);
+
+    // Verify tracker indices/ids are correct.
+    EXPECT_EQ(newConditionTrackers[simple1Index]->getConditionId(), simple1Id);
+    EXPECT_EQ(newConditionTrackers[simple1Index]->mIndex, simple1Index);
+    EXPECT_TRUE(newConditionTrackers[simple1Index]->IsSimpleCondition());
+    EXPECT_EQ(newConditionTrackers[simple2Index]->getConditionId(), simple2Id);
+    EXPECT_EQ(newConditionTrackers[simple2Index]->mIndex, simple2Index);
+    EXPECT_TRUE(newConditionTrackers[simple2Index]->IsSimpleCondition());
+    EXPECT_EQ(newConditionTrackers[simple3Index]->getConditionId(), simple3Id);
+    EXPECT_EQ(newConditionTrackers[simple3Index]->mIndex, simple3Index);
+    EXPECT_TRUE(newConditionTrackers[simple3Index]->IsSimpleCondition());
+    EXPECT_EQ(newConditionTrackers[combination1Index]->getConditionId(), combination1Id);
+    EXPECT_EQ(newConditionTrackers[combination1Index]->mIndex, combination1Index);
+    EXPECT_FALSE(newConditionTrackers[combination1Index]->IsSimpleCondition());
+    EXPECT_EQ(newConditionTrackers[combination2Index]->getConditionId(), combination2Id);
+    EXPECT_EQ(newConditionTrackers[combination2Index]->mIndex, combination2Index);
+    EXPECT_FALSE(newConditionTrackers[combination2Index]->IsSimpleCondition());
+    EXPECT_EQ(newConditionTrackers[combination4Index]->getConditionId(), combination4Id);
+    EXPECT_EQ(newConditionTrackers[combination4Index]->mIndex, combination4Index);
+    EXPECT_FALSE(newConditionTrackers[combination4Index]->IsSimpleCondition());
+
+    // Verify preserved trackers have indices updated.
+    SimpleConditionTracker* simpleTracker1 =
+            static_cast<SimpleConditionTracker*>(newConditionTrackers[simple1Index].get());
+    EXPECT_EQ(simpleTracker1->mStartLogMatcherIndex, matcher1Index);
+    EXPECT_EQ(simpleTracker1->mStopLogMatcherIndex, matcher2Index);
+    EXPECT_EQ(simpleTracker1->mStopAllLogMatcherIndex, -1);
+
+    SimpleConditionTracker* simpleTracker2 =
+            static_cast<SimpleConditionTracker*>(newConditionTrackers[simple2Index].get());
+    EXPECT_EQ(simpleTracker2->mStartLogMatcherIndex, matcher3Index);
+    EXPECT_EQ(simpleTracker2->mStopLogMatcherIndex, matcher4Index);
+    EXPECT_EQ(simpleTracker2->mStopAllLogMatcherIndex, -1);
+
+    CombinationConditionTracker* combinationTracker1 = static_cast<CombinationConditionTracker*>(
+            newConditionTrackers[combination1Index].get());
+    EXPECT_THAT(combinationTracker1->mChildren, UnorderedElementsAre(simple1Index, simple2Index));
+    EXPECT_THAT(combinationTracker1->mUnSlicedChildren,
+                UnorderedElementsAre(simple1Index, simple2Index));
+    EXPECT_THAT(combinationTracker1->mSlicedChildren, IsEmpty());
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
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
index 4e97eaf..e6583c9 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -24,7 +24,7 @@
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.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"
@@ -383,9 +383,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildConfigWithDifferentPredicates();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -400,9 +401,9 @@
 
     EXPECT_TRUE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
     ASSERT_EQ(4u, allMetricProducers.size());
@@ -432,9 +433,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildGoodConfig();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -449,9 +451,9 @@
 
     EXPECT_TRUE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
     ASSERT_EQ(1u, allMetricProducers.size());
@@ -469,9 +471,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildDimensionMetricsWithMultiTags();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -486,9 +489,9 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
@@ -500,9 +503,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildCircleMatchers();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -517,9 +521,9 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
@@ -531,9 +535,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildMissingMatchers();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -547,9 +552,9 @@
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
@@ -561,9 +566,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildMissingPredicate();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -577,9 +583,9 @@
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
@@ -591,9 +597,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildCirclePredicates();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -608,9 +615,9 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
@@ -622,9 +629,10 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     StatsdConfig config = buildAlertWithUnknownMetric();
     set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> atomMatchingTrackerMap;
     vector<sp<ConditionTracker>> allConditionTrackers;
+    unordered_map<int64_t, int> conditionTrackerMap;
     vector<sp<MetricProducer>> allMetricProducers;
     std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     std::vector<sp<AlarmTracker>> allAlarmTrackers;
@@ -639,21 +647,22 @@
 
     EXPECT_FALSE(initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, logTrackerMap,
-            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
-            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap,
+            allConditionTrackers, conditionTrackerMap, allMetricProducers, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
             metricsWithActivation, noReportMetricIds));
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerInvalidMatcher) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerInvalidMatcher) {
     sp<UidMap> uidMap = new UidMap();
     AtomMatcher matcher;
+    // Matcher has no contents_case (simple/combination), so it is invalid.
     matcher.set_id(21);
-    EXPECT_EQ(createLogTracker(matcher, 0, uidMap), nullptr);
+    EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap), nullptr);
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerSimple) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple) {
     int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
@@ -666,7 +675,7 @@
     simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
             android::view::DisplayStateEnum::DISPLAY_STATE_ON);
 
-    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
     EXPECT_NE(tracker, nullptr);
 
     EXPECT_TRUE(tracker->mInitialized);
@@ -677,7 +686,7 @@
     EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1);
 }
 
-TEST(MetricsManagerTest, TestCreateLogTrackerCombination) {
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination) {
     int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
@@ -688,7 +697,7 @@
     combination->add_matcher(123);
     combination->add_matcher(223);
 
-    sp<LogMatchingTracker> tracker = createLogTracker(matcher, index, uidMap);
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
     EXPECT_NE(tracker, nullptr);
 
     // Combination matchers need to be initialized first.
@@ -699,6 +708,65 @@
     ASSERT_EQ(atomIds.size(), 0);
 }
 
+TEST(MetricsManagerTest, TestCreateConditionTrackerInvalid) {
+    const ConfigKey key(123, 456);
+    // Predicate has no contents_case (simple/combination), so it is invalid.
+    Predicate predicate;
+    predicate.set_id(21);
+    unordered_map<int64_t, int> atomTrackerMap;
+    EXPECT_EQ(createConditionTracker(key, predicate, 0, atomTrackerMap), nullptr);
+}
+
+TEST(MetricsManagerTest, TestCreateConditionTrackerSimple) {
+    int index = 1;
+    int64_t id = 987;
+    const ConfigKey key(123, 456);
+
+    int startMatcherIndex = 2, stopMatcherIndex = 0, stopAllMatcherIndex = 1;
+    int64_t startMatcherId = 246, stopMatcherId = 153, stopAllMatcherId = 975;
+
+    Predicate predicate;
+    predicate.set_id(id);
+    SimplePredicate* simplePredicate = predicate.mutable_simple_predicate();
+    simplePredicate->set_start(startMatcherId);
+    simplePredicate->set_stop(stopMatcherId);
+    simplePredicate->set_stop_all(stopAllMatcherId);
+
+    unordered_map<int64_t, int> atomTrackerMap;
+    atomTrackerMap[startMatcherId] = startMatcherIndex;
+    atomTrackerMap[stopMatcherId] = stopMatcherIndex;
+    atomTrackerMap[stopAllMatcherId] = stopAllMatcherIndex;
+
+    sp<ConditionTracker> tracker = createConditionTracker(key, predicate, index, atomTrackerMap);
+    EXPECT_EQ(tracker->getConditionId(), id);
+    EXPECT_EQ(tracker->isSliced(), false);
+    EXPECT_TRUE(tracker->IsSimpleCondition());
+    const set<int>& interestedMatchers = tracker->getAtomMatchingTrackerIndex();
+    ASSERT_EQ(interestedMatchers.size(), 3);
+    ASSERT_EQ(interestedMatchers.count(startMatcherIndex), 1);
+    ASSERT_EQ(interestedMatchers.count(stopMatcherIndex), 1);
+    ASSERT_EQ(interestedMatchers.count(stopAllMatcherIndex), 1);
+}
+
+TEST(MetricsManagerTest, TestCreateConditionTrackerCombination) {
+    int index = 1;
+    int64_t id = 987;
+    const ConfigKey key(123, 456);
+
+    Predicate predicate;
+    predicate.set_id(id);
+    Predicate_Combination* combinationPredicate = predicate.mutable_combination();
+    combinationPredicate->set_operation(LogicalOperation::AND);
+    combinationPredicate->add_predicate(888);
+    combinationPredicate->add_predicate(777);
+
+    // Combination conditions must be initialized to set most state.
+    unordered_map<int64_t, int> atomTrackerMap;
+    sp<ConditionTracker> tracker = createConditionTracker(key, predicate, index, atomTrackerMap);
+    EXPECT_EQ(tracker->getConditionId(), id);
+    EXPECT_FALSE(tracker->IsSimpleCondition());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 0be983f..1761d5d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -16,7 +16,7 @@
 
 #include <aidl/android/util/StatsEventParcel.h>
 
-#include "matchers/SimpleLogMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "stats_event.h"
 
 using aidl::android::util::StatsEventParcel;
@@ -1008,8 +1008,8 @@
     }
     uint64_t matcherHash = 0x12345678;
     int64_t matcherId = 678;
-    return new EventMatcherWizard({new SimpleLogMatchingTracker(matcherId, matcherIndex,
-                                                                matcherHash, atomMatcher, uidMap)});
+    return new EventMatcherWizard({new SimpleAtomMatchingTracker(
+            matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)});
 }
 
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
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/Android.bp b/config/Android.bp
index 0fb56cb..8dd409b 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -13,6 +13,6 @@
 // limitations under the License.
 
 filegroup {
-    name: "preloaded-classes-blacklist",
-    srcs: ["preloaded-classes-blacklist"],
+    name: "preloaded-classes-denylist",
+    srcs: ["preloaded-classes-denylist"],
 }
diff --git a/config/generate-preloaded-classes.sh b/config/generate-preloaded-classes.sh
index 0ad3a02..b17a366 100755
--- a/config/generate-preloaded-classes.sh
+++ b/config/generate-preloaded-classes.sh
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 if [ "$#" -lt 2 ]; then
-  echo "Usage $0 <input classes file> <blacklist file> [extra classes files]"
+  echo "Usage $0 <input classes file> <denylist file> [extra classes files]"
   exit 1
 fi
 
@@ -31,9 +31,9 @@
 #"
 
 input=$1
-blacklist=$2
+denylist=$2
 shift 2
 extra_classes_files=("$@")
 
 # Disable locale to enable lexicographical sorting
-LC_ALL=C sort "$input" "${extra_classes_files[@]}" | uniq | grep -f "$blacklist" -v -F -x | grep -v "\$NoPreloadHolder"
+LC_ALL=C sort "$input" "${extra_classes_files[@]}" | uniq | grep -f "$denylist" -v -F -x | grep -v "\$NoPreloadHolder"
diff --git a/config/preloaded-classes-blacklist b/config/preloaded-classes-denylist
similarity index 100%
rename from config/preloaded-classes-blacklist
rename to config/preloaded-classes-denylist
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 267b818..caca05a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -614,7 +614,7 @@
                     throw new IllegalStateException(
                             "Received config update for non-existing activity");
                 }
-                activity.mMainThread.handleActivityConfigurationChanged(this, overrideConfig,
+                activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
                         newDisplayId);
             };
         }
@@ -2242,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
@@ -3475,9 +3475,13 @@
     }
 
     @Override
-    public void handleStartActivity(ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = mActivities.get(token);
         final Activity activity = r.activity;
+        if (r.activity == null) {
+            // TODO(lifecycler): What do we do in this case?
+            return;
+        }
         if (!r.stopped) {
             throw new IllegalStateException("Can't start activity that is not stopped.");
         }
@@ -3683,7 +3687,12 @@
     }
 
     @Override
-    public void handleNewIntent(ActivityClientRecord r, List<ReferrerIntent> intents) {
+    public void handleNewIntent(IBinder token, List<ReferrerIntent> intents) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            return;
+        }
+
         checkAndBlockForNetworkAccess();
         deliverNewIntents(r, intents);
     }
@@ -3872,7 +3881,13 @@
     }
 
     @Override
-    public void handlePictureInPictureRequested(ActivityClientRecord r) {
+    public void handlePictureInPictureRequested(IBinder token) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            Log.w(TAG, "Activity to request PIP to no longer exists");
+            return;
+        }
+
         final boolean receivedByApp = r.activity.onPictureInPictureRequested();
         if (!receivedByApp) {
             // Previous recommendation was for apps to enter picture-in-picture in
@@ -4402,21 +4417,22 @@
 
     /**
      * Resume the activity.
-     * @param r Target activity record.
+     * @param token Target activity token.
      * @param finalStateRequest Flag indicating if this is part of final state resolution for a
      *                          transaction.
      * @param reason Reason for performing the action.
      *
-     * @return {@code true} that was resumed, {@code false} otherwise.
+     * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
      */
     @VisibleForTesting
-    public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
+    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
             String reason) {
+        final ActivityClientRecord r = mActivities.get(token);
         if (localLOGV) {
             Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
         }
-        if (r.activity.mFinished) {
-            return false;
+        if (r == null || r.activity.mFinished) {
+            return null;
         }
         if (r.getLifecycleState() == ON_RESUME) {
             if (!finalStateRequest) {
@@ -4430,7 +4446,7 @@
                 // handle two resume requests for the final state. For cases other than this
                 // one, we don't expect it to happen.
             }
-            return false;
+            return null;
         }
         if (finalStateRequest) {
             r.hideForNow = false;
@@ -4461,7 +4477,7 @@
                         + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
             }
         }
-        return true;
+        return r;
     }
 
     static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
@@ -4482,19 +4498,20 @@
     }
 
     @Override
-    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
-            boolean isForward, String reason) {
+    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
+            String reason) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
         mSomeActivitiesChanged = true;
 
         // TODO Push resumeArgs into the activity for consideration
-        // skip below steps for double-resume and r.mFinish = true case.
-        if (!performResumeActivity(r, finalStateRequest, reason)) {
+        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
+        if (r == null) {
+            // We didn't actually resume the activity, so skipping any follow-up actions.
             return;
         }
-        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
+        if (mActivitiesToBeDestroyed.containsKey(token)) {
             // Although the activity is resumed, it is going to be destroyed. So the following
             // UI operations are unnecessary and also prevents exception because its token may
             // be gone that window manager cannot recognize it. All necessary cleanup actions
@@ -4612,8 +4629,13 @@
 
 
     @Override
-    public void handleTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
-            String reason) {
+    public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r == null || r.activity == null) {
+            Slog.w(TAG, "Not found target activity to report position change for token: " + token);
+            return;
+        }
+
         if (DEBUG_ORDER) {
             Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
         }
@@ -4646,20 +4668,23 @@
     }
 
     @Override
-    public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
+    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
             int configChanges, PendingTransactionActions pendingActions, String reason) {
-        if (userLeaving) {
-            performUserLeavingActivity(r);
-        }
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            if (userLeaving) {
+                performUserLeavingActivity(r);
+            }
 
-        r.activity.mConfigChangeFlags |= configChanges;
-        performPauseActivity(r, finished, reason, pendingActions);
+            r.activity.mConfigChangeFlags |= configChanges;
+            performPauseActivity(r, finished, reason, pendingActions);
 
-        // Make sure any pending writes are now committed.
-        if (r.isPreHoneycomb()) {
-            QueuedWork.waitToFinish();
+            // Make sure any pending writes are now committed.
+            if (r.isPreHoneycomb()) {
+                QueuedWork.waitToFinish();
+            }
+            mSomeActivitiesChanged = true;
         }
-        mSomeActivitiesChanged = true;
     }
 
     final void performUserLeavingActivity(ActivityClientRecord r) {
@@ -4756,11 +4781,8 @@
         r.setState(ON_PAUSE);
     }
 
-    // TODO(b/127877792): Make LocalActivityManager call performStopActivityInner. We cannot do this
-    // since it's a high usage hidden API.
     /** Called from {@link LocalActivityManager}. */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 127877792,
-            publicAlternatives = "{@code N/A}")
+    @UnsupportedAppUsage
     final void performStopActivity(IBinder token, boolean saveState, String reason) {
         ActivityClientRecord r = mActivities.get(token);
         performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
@@ -4801,37 +4823,39 @@
     private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
             boolean saveState, boolean finalStateRequest, String reason) {
         if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
-        if (r.stopped) {
-            if (r.activity.mFinished) {
-                // If we are finishing, we won't call onResume() in certain
-                // cases.  So here we likewise don't want to call onStop()
-                // if the activity isn't resumed.
-                return;
+        if (r != null) {
+            if (r.stopped) {
+                if (r.activity.mFinished) {
+                    // If we are finishing, we won't call onResume() in certain
+                    // cases.  So here we likewise don't want to call onStop()
+                    // if the activity isn't resumed.
+                    return;
+                }
+                if (!finalStateRequest) {
+                    final RuntimeException e = new RuntimeException(
+                            "Performing stop of activity that is already stopped: "
+                                    + r.intent.getComponent().toShortString());
+                    Slog.e(TAG, e.getMessage(), e);
+                    Slog.e(TAG, r.getStateString());
+                }
             }
-            if (!finalStateRequest) {
-                final RuntimeException e = new RuntimeException(
-                        "Performing stop of activity that is already stopped: "
-                                + r.intent.getComponent().toShortString());
-                Slog.e(TAG, e.getMessage(), e);
-                Slog.e(TAG, r.getStateString());
-            }
-        }
 
-        // One must first be paused before stopped...
-        performPauseActivityIfNeeded(r, reason);
+            // One must first be paused before stopped...
+            performPauseActivityIfNeeded(r, reason);
 
-        if (info != null) {
-            try {
-                // First create a thumbnail for the activity...
-                // For now, don't create the thumbnail here; we are
-                // doing that by doing a screen snapshot.
-                info.setDescription(r.activity.onCreateDescription());
-            } catch (Exception e) {
-                if (!mInstrumentation.onException(r.activity, e)) {
-                    throw new RuntimeException(
-                            "Unable to save state of activity "
-                            + r.intent.getComponent().toShortString()
-                            + ": " + e.toString(), e);
+            if (info != null) {
+                try {
+                    // First create a thumbnail for the activity...
+                    // For now, don't create the thumbnail here; we are
+                    // doing that by doing a screen snapshot.
+                    info.setDescription(r.activity.onCreateDescription());
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to save state of activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
                 }
             }
 
@@ -4903,8 +4927,9 @@
     }
 
     @Override
-    public void handleStopActivity(ActivityClientRecord r, int configChanges,
+    public void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
+        final ActivityClientRecord r = mActivities.get(token);
         r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
@@ -4940,7 +4965,8 @@
     }
 
     @Override
-    public void performRestartActivity(ActivityClientRecord r, boolean start) {
+    public void performRestartActivity(IBinder token, boolean start) {
+        ActivityClientRecord r = mActivities.get(token);
         if (r.stopped) {
             r.activity.performRestart(start, "performRestartActivity");
             if (start) {
@@ -5027,101 +5053,107 @@
     }
 
     @Override
-    public void handleSendResult(ActivityClientRecord r, List<ResultInfo> results, String reason) {
+    public void handleSendResult(IBinder token, List<ResultInfo> results, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
         if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
-        final boolean resumed = !r.paused;
-        if (!r.activity.mFinished && r.activity.mDecor != null
-                && r.hideForNow && resumed) {
-            // We had hidden the activity because it started another
-            // one...  we have gotten a result back and we are not
-            // paused, so make sure our window is visible.
-            updateVisibility(r, true);
+        if (r != null) {
+            final boolean resumed = !r.paused;
+            if (!r.activity.mFinished && r.activity.mDecor != null
+                    && r.hideForNow && resumed) {
+                // We had hidden the activity because it started another
+                // one...  we have gotten a result back and we are not
+                // paused, so make sure our window is visible.
+                updateVisibility(r, true);
+            }
+            if (resumed) {
+                try {
+                    // Now we are idle.
+                    r.activity.mCalled = false;
+                    mInstrumentation.callActivityOnPause(r.activity);
+                    if (!r.activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString()
+                            + " did not call through to super.onPause()");
+                    }
+                } catch (SuperNotCalledException e) {
+                    throw e;
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to pause activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+            checkAndBlockForNetworkAccess();
+            deliverResults(r, results, reason);
+            if (resumed) {
+                r.activity.performResume(false, reason);
+            }
         }
-        if (resumed) {
+    }
+
+    /** Core implementation of activity destroy call. */
+    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
+            int configChanges, boolean getNonConfigInstance, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        Class<? extends Activity> activityClass = null;
+        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
+        if (r != null) {
+            activityClass = r.activity.getClass();
+            r.activity.mConfigChangeFlags |= configChanges;
+            if (finishing) {
+                r.activity.mFinished = true;
+            }
+
+            performPauseActivityIfNeeded(r, "destroy");
+
+            if (!r.stopped) {
+                callActivityOnStop(r, false /* saveState */, "destroy");
+            }
+            if (getNonConfigInstance) {
+                try {
+                    r.lastNonConfigurationInstances
+                            = r.activity.retainNonConfigurationInstances();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to retain activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
             try {
-                // Now we are idle.
                 r.activity.mCalled = false;
-                mInstrumentation.callActivityOnPause(r.activity);
+                mInstrumentation.callActivityOnDestroy(r.activity);
                 if (!r.activity.mCalled) {
                     throw new SuperNotCalledException(
-                        "Activity " + r.intent.getComponent().toShortString()
-                        + " did not call through to super.onPause()");
+                        "Activity " + safeToComponentShortString(r.intent) +
+                        " did not call through to super.onDestroy()");
+                }
+                if (r.window != null) {
+                    r.window.closeAllPanels();
                 }
             } catch (SuperNotCalledException e) {
                 throw e;
             } catch (Exception e) {
                 if (!mInstrumentation.onException(r.activity, e)) {
                     throw new RuntimeException(
-                            "Unable to pause activity "
-                            + r.intent.getComponent().toShortString()
-                            + ": " + e.toString(), e);
-                }
-            }
-        }
-        checkAndBlockForNetworkAccess();
-        deliverResults(r, results, reason);
-        if (resumed) {
-            r.activity.performResume(false, reason);
-        }
-    }
-
-    /** Core implementation of activity destroy call. */
-    ActivityClientRecord performDestroyActivity(ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason) {
-        Class<? extends Activity> activityClass = null;
-        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
-        activityClass = r.activity.getClass();
-        r.activity.mConfigChangeFlags |= configChanges;
-        if (finishing) {
-            r.activity.mFinished = true;
-        }
-
-        performPauseActivityIfNeeded(r, "destroy");
-
-        if (!r.stopped) {
-            callActivityOnStop(r, false /* saveState */, "destroy");
-        }
-        if (getNonConfigInstance) {
-            try {
-                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
-            } catch (Exception e) {
-                if (!mInstrumentation.onException(r.activity, e)) {
-                    throw new RuntimeException(
-                            "Unable to retain activity "
-                            + r.intent.getComponent().toShortString()
+                            "Unable to destroy activity " + safeToComponentShortString(r.intent)
                             + ": " + e.toString(), e);
                 }
             }
             r.setState(ON_DESTROY);
             mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         }
-        try {
-            r.activity.mCalled = false;
-            mInstrumentation.callActivityOnDestroy(r.activity);
-            if (!r.activity.mCalled) {
-                throw new SuperNotCalledException(
-                    "Activity " + safeToComponentShortString(r.intent)
-                            + " did not call through to super.onDestroy()");
-            }
-            if (r.window != null) {
-                r.window.closeAllPanels();
-            }
-        } catch (SuperNotCalledException e) {
-            throw e;
-        } catch (Exception e) {
-            if (!mInstrumentation.onException(r.activity, e)) {
-                throw new RuntimeException(
-                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
-                        + ": " + e.toString(), e);
-            }
-        }
-        r.setState(ON_DESTROY);
         schedulePurgeIdler();
         // updatePendingActivityConfiguration() reads from mActivities to update
         // ActivityClientRecord which runs in a different thread. Protect modifications to
         // mActivities to avoid race.
         synchronized (mResourcesManager) {
-            mActivities.remove(r.token);
+            mActivities.remove(token);
         }
         StrictMode.decrementExpectedActivityCount(activityClass);
         return r;
@@ -5138,67 +5170,70 @@
     }
 
     @Override
-    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason) {
-        r = performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
-        cleanUpPendingRemoveWindows(r, finishing);
-        WindowManager wm = r.activity.getWindowManager();
-        View v = r.activity.mDecor;
-        if (v != null) {
-            if (r.activity.mVisibleFromServer) {
-                mNumVisibleActivities--;
-            }
-            IBinder wtoken = v.getWindowToken();
-            if (r.activity.mWindowAdded) {
-                if (r.mPreserveWindow) {
-                    // Hold off on removing this until the new activity's
-                    // window is being added.
-                    r.mPendingRemoveWindow = r.window;
-                    r.mPendingRemoveWindowManager = wm;
-                    // We can only keep the part of the view hierarchy that we control,
-                    // everything else must be removed, because it might not be able to
-                    // behave properly when activity is relaunching.
-                    r.window.clearContentView();
-                } else {
-                    wm.removeViewImmediate(v);
+        ActivityClientRecord r = performDestroyActivity(token, finishing,
+                configChanges, getNonConfigInstance, reason);
+        if (r != null) {
+            cleanUpPendingRemoveWindows(r, finishing);
+            WindowManager wm = r.activity.getWindowManager();
+            View v = r.activity.mDecor;
+            if (v != null) {
+                if (r.activity.mVisibleFromServer) {
+                    mNumVisibleActivities--;
                 }
+                IBinder wtoken = v.getWindowToken();
+                if (r.activity.mWindowAdded) {
+                    if (r.mPreserveWindow) {
+                        // Hold off on removing this until the new activity's
+                        // window is being added.
+                        r.mPendingRemoveWindow = r.window;
+                        r.mPendingRemoveWindowManager = wm;
+                        // We can only keep the part of the view hierarchy that we control,
+                        // everything else must be removed, because it might not be able to
+                        // behave properly when activity is relaunching.
+                        r.window.clearContentView();
+                    } else {
+                        wm.removeViewImmediate(v);
+                    }
+                }
+                if (wtoken != null && r.mPendingRemoveWindow == null) {
+                    WindowManagerGlobal.getInstance().closeAll(wtoken,
+                            r.activity.getClass().getName(), "Activity");
+                } else if (r.mPendingRemoveWindow != null) {
+                    // We're preserving only one window, others should be closed so app views
+                    // will be detached before the final tear down. It should be done now because
+                    // some components (e.g. WebView) rely on detach callbacks to perform receiver
+                    // unregister and other cleanup.
+                    WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
+                            r.activity.getClass().getName(), "Activity");
+                }
+                r.activity.mDecor = null;
             }
-            if (wtoken != null && r.mPendingRemoveWindow == null) {
-                WindowManagerGlobal.getInstance().closeAll(wtoken,
-                        r.activity.getClass().getName(), "Activity");
-            } else if (r.mPendingRemoveWindow != null) {
-                // We're preserving only one window, others should be closed so app views
-                // will be detached before the final tear down. It should be done now because
-                // some components (e.g. WebView) rely on detach callbacks to perform receiver
-                // unregister and other cleanup.
-                WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
+            if (r.mPendingRemoveWindow == null) {
+                // If we are delaying the removal of the activity window, then
+                // we can't clean up all windows here.  Note that we can't do
+                // so later either, which means any windows that aren't closed
+                // by the app will leak.  Well we try to warning them a lot
+                // about leaking windows, because that is a bug, so if they are
+                // using this recreate facility then they get to live with leaks.
+                WindowManagerGlobal.getInstance().closeAll(token,
                         r.activity.getClass().getName(), "Activity");
             }
-            r.activity.mDecor = null;
-        }
-        if (r.mPendingRemoveWindow == null) {
-            // If we are delaying the removal of the activity window, then
-            // we can't clean up all windows here.  Note that we can't do
-            // so later either, which means any windows that aren't closed
-            // by the app will leak.  Well we try to warning them a lot
-            // about leaking windows, because that is a bug, so if they are
-            // using this recreate facility then they get to live with leaks.
-            WindowManagerGlobal.getInstance().closeAll(r.token,
-                    r.activity.getClass().getName(), "Activity");
-        }
 
-        // Mocked out contexts won't be participating in the normal
-        // process lifecycle, but if we're running with a proper
-        // ApplicationContext we need to have it tear down things
-        // cleanly.
-        Context c = r.activity.getBaseContext();
-        if (c instanceof ContextImpl) {
-            ((ContextImpl) c).scheduleFinalCleanup(
-                    r.activity.getClass().getName(), "Activity");
+            // Mocked out contexts won't be participating in the normal
+            // process lifecycle, but if we're running with a proper
+            // ApplicationContext we need to have it tear down things
+            // cleanly.
+            Context c = r.activity.getBaseContext();
+            if (c instanceof ContextImpl) {
+                ((ContextImpl) c).scheduleFinalCleanup(
+                        r.activity.getClass().getName(), "Activity");
+            }
         }
         if (finishing) {
             try {
-                ActivityTaskManager.getService().activityDestroyed(r.token);
+                ActivityTaskManager.getService().activityDestroyed(token);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -5421,7 +5456,7 @@
             callActivityOnStop(r, true /* saveState */, reason);
         }
 
-        handleDestroyActivity(r, false, configChanges, true, reason);
+        handleDestroyActivity(r.token, false, configChanges, true, reason);
 
         r.activity = null;
         r.window = null;
@@ -5449,10 +5484,12 @@
     }
 
     @Override
-    public void reportRelaunch(ActivityClientRecord r, PendingTransactionActions pendingActions) {
+    public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
         try {
-            ActivityTaskManager.getService().activityRelaunched(r.token);
-            if (pendingActions.shouldReportRelaunchToWindowManager() && r.window != null) {
+            ActivityTaskManager.getService().activityRelaunched(token);
+            final ActivityClientRecord r = mActivities.get(token);
+            if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
+                    && r.window != null) {
                 r.window.reportActivityRelaunched();
             }
         } catch (RemoteException e) {
@@ -5611,7 +5648,18 @@
      */
     private Configuration performActivityConfigurationChanged(Activity activity,
             Configuration newConfig, Configuration amOverrideConfig, int displayId) {
+        if (activity == null) {
+            throw new IllegalArgumentException("No activity provided.");
+        }
         final IBinder activityToken = activity.getActivityToken();
+        if (activityToken == null) {
+            throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
+        }
+
+        // WindowConfiguration differences aren't considered as public, check it separately.
+        // 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;
@@ -5649,8 +5697,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);
@@ -5666,11 +5713,6 @@
         }
 
         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) {
@@ -5911,8 +5953,20 @@
      * processing any configurations older than {@code overrideConfig}.
      */
     @Override
-    public void updatePendingActivityConfiguration(ActivityClientRecord r,
+    public void updatePendingActivityConfiguration(IBinder activityToken,
             Configuration overrideConfig) {
+        final ActivityClientRecord r;
+        synchronized (mResourcesManager) {
+            r = mActivities.get(activityToken);
+        }
+
+        if (r == null) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.w(TAG, "Not found target activity to update its pending config.");
+            }
+            return;
+        }
+
         synchronized (r) {
             if (r.mPendingOverrideConfig != null
                     && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
@@ -5932,14 +5986,21 @@
      * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
      * a newer config than {@code overrideConfig}.
      *
-     * @param r Target activity record.
+     * @param activityToken Target activity token.
      * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
      */
     @Override
-    public void handleActivityConfigurationChanged(ActivityClientRecord r,
+    public void handleActivityConfigurationChanged(IBinder activityToken,
             @NonNull Configuration overrideConfig, int displayId) {
+        ActivityClientRecord r = mActivities.get(activityToken);
+        // Check input params.
+        if (r == null || r.activity == null) {
+            if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
+            return;
+        }
+
         synchronized (r) {
             if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) {
                 if (DEBUG_CONFIGURATION) {
@@ -5952,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) {
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 95136bb..7087b60 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1145,9 +1145,32 @@
     /** @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;
+
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_RECORD_AUDIO_HOTWORD = 102;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 100;
+    public static final int _NUM_OP = 103;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1469,6 +1492,26 @@
     @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";
+
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword";
+
     /** {@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 +1701,9 @@
             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
+            OP_RECORD_AUDIO_HOTWORD,            // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -1764,6 +1810,9 @@
             OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
             OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
             OPSTR_NO_ISOLATED_STORAGE,
+            OPSTR_PHONE_CALL_MICROPHONE,
+            OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_RECORD_AUDIO_HOTWORD,
     };
 
     /**
@@ -1871,6 +1920,9 @@
             "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
             "AUTO_REVOKE_MANAGED_BY_INSTALLER",
             "NO_ISOLATED_STORAGE",
+            "PHONE_CALL_MICROPHONE",
+            "PHONE_CALL_CAMERA",
+            "RECORD_AUDIO_HOTWORD",
     };
 
     /**
@@ -1979,6 +2031,9 @@
             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
+            null, // no permission for OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2087,6 +2142,9 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_MICROPHONE
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2194,6 +2252,9 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_CAMERA
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2300,6 +2361,9 @@
             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
+            AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2410,6 +2474,9 @@
             false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             true, // NO_ISOLATED_STORAGE
+            false, // PHONE_CALL_MICROPHONE
+            false, // PHONE_CALL_CAMERA
+            false, // RECORD_AUDIO_HOTWORD
     };
 
     /**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 676c6c0..2780036 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -73,6 +73,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelableException;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
@@ -96,7 +97,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;
@@ -108,7 +108,11 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -136,6 +140,12 @@
     // Default flags to use with PackageManager when no flags are given.
     private static final int sDefaultFlags = GET_SHARED_LIBRARY_FILES;
 
+    /** Default set of checksums - includes all available checksums.
+     * @see PackageManager#getChecksums  */
+    private static final int DEFAULT_CHECKSUMS =
+            WHOLE_MERKLE_ROOT_4K_SHA256 | WHOLE_MD5 | WHOLE_SHA1 | WHOLE_SHA256 | WHOLE_SHA512
+                    | PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512;
+
     // Name of the resource which provides background permission button string
     public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
             "app_permission_button_allow_always";
@@ -946,6 +956,39 @@
         }
     }
 
+    private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
+            CertificateEncodingException {
+        if (certs == null) {
+            return null;
+        }
+        List<byte[]> result = new ArrayList<>(certs.size());
+        for (Certificate cert : certs) {
+            if (!(cert instanceof X509Certificate)) {
+                throw new CertificateEncodingException("Only X509 certificates supported.");
+            }
+            result.add(cert.getEncoded());
+        }
+        return result;
+    }
+
+    @Override
+    public void getChecksums(@NonNull String packageName, boolean includeSplits,
+            @FileChecksumKind int required, @Nullable List<Certificate> trustedInstallers,
+            @NonNull IntentSender statusReceiver)
+            throws CertificateEncodingException, IOException, NameNotFoundException {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(statusReceiver);
+        try {
+            mPM.getChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
+                    encodeCertificates(trustedInstallers), statusReceiver, getUserId());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Wrap the cached value in a class that does deep compares on string
      * arrays.  The comparison is needed only for the verification mode of
@@ -1748,7 +1791,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/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index ac50676..2df756e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -15,8 +15,6 @@
  */
 package android.app;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.PendingTransactionActions;
@@ -91,38 +89,37 @@
     public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
 
     /** Destroy the activity. */
-    public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason);
+    public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+            boolean getNonConfigInstance, String reason);
 
     /** Pause the activity. */
-    public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
-            boolean userLeaving, int configChanges, PendingTransactionActions pendingActions,
-            String reason);
+    public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+            int configChanges, PendingTransactionActions pendingActions, String reason);
 
     /**
      * Resume the activity.
-     * @param r Target activity record.
+     * @param token Target activity token.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param isForward Flag indicating if next transition is forward.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleResumeActivity(@NonNull ActivityClientRecord r,
-            boolean finalStateRequest, boolean isForward, String reason);
+    public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest,
+            boolean isForward, String reason);
 
     /**
      * Notify the activity about top resumed state change.
-     * @param r Target activity record.
+     * @param token Target activity token.
      * @param isTopResumedActivity Current state of the activity, {@code true} if it's the
      *                             topmost resumed activity in the system, {@code false} otherwise.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleTopResumedActivityChanged(@NonNull ActivityClientRecord r,
+    public abstract void handleTopResumedActivityChanged(IBinder token,
             boolean isTopResumedActivity, String reason);
 
     /**
      * Stop the activity.
-     * @param r Target activity record.
+     * @param token Target activity token.
      * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
@@ -130,40 +127,38 @@
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
+    public abstract void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
     public abstract void reportStop(PendingTransactionActions pendingActions);
 
     /** Restart the activity after it was stopped. */
-    public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
+    public abstract void performRestartActivity(IBinder token, boolean start);
 
     /** Set pending activity configuration in case it will be updated by other transaction item. */
-    public abstract void updatePendingActivityConfiguration(@NonNull ActivityClientRecord r,
+    public abstract void updatePendingActivityConfiguration(IBinder activityToken,
             Configuration overrideConfig);
 
     /** Deliver activity (override) configuration change. */
-    public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
+    public abstract void handleActivityConfigurationChanged(IBinder activityToken,
             Configuration overrideConfig, int displayId);
 
     /** Deliver result from another activity. */
-    public abstract void handleSendResult(
-            @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason);
+    public abstract void handleSendResult(IBinder token, List<ResultInfo> results, String reason);
 
     /** Deliver new intent. */
-    public abstract void handleNewIntent(
-            @NonNull ActivityClientRecord r, List<ReferrerIntent> intents);
+    public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents);
 
     /** Request that an activity enter picture-in-picture. */
-    public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
+    public abstract void handlePictureInPictureRequested(IBinder token);
 
     /** Perform activity launch. */
-    public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
+    public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
 
     /** Perform activity start. */
-    public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
+    public abstract void handleStartActivity(IBinder token,
             PendingTransactionActions pendingActions);
 
     /** Get package info. */
@@ -181,7 +176,7 @@
      * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
      * provided token.
      */
-    public abstract ActivityClientRecord getActivityClient(IBinder token);
+    public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
 
     /**
      * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
@@ -196,7 +191,7 @@
      * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
      *         relaunch, or {@code null} if relaunch cancelled.
      */
-    public abstract ActivityClientRecord prepareRelaunchActivity(IBinder token,
+    public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
             int configChanges, MergedConfiguration config, boolean preserveWindow);
 
@@ -205,15 +200,14 @@
      * @param r Activity client record prepared for relaunch.
      * @param pendingActions Pending actions to be used on later stages of activity transaction.
      * */
-    public abstract void handleRelaunchActivity(@NonNull ActivityClientRecord r,
+    public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
             PendingTransactionActions pendingActions);
 
     /**
      * Report that relaunch request was handled.
-     * @param r Target activity record.
+     * @param token Target activity token.
      * @param pendingActions Pending actions initialized on earlier stages of activity transaction.
      *                       Used to check if we should report relaunch to WM.
      * */
-    public abstract void reportRelaunch(@NonNull ActivityClientRecord r,
-            PendingTransactionActions pendingActions);
+    public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f6b5334..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;
 
@@ -1698,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("!")) {
@@ -2246,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 {
@@ -2261,7 +2283,7 @@
                 splitResDirs,
                 pi.getOverlayDirs(),
                 pi.getApplicationInfo().sharedLibraryFiles,
-                displayId,
+                overrideDisplayId,
                 overrideConfig,
                 compatInfo,
                 classLoader,
@@ -2278,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;
@@ -2313,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;
@@ -2348,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,
@@ -2370,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();
@@ -2393,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;
     }
 
@@ -2415,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;
     }
 
@@ -2759,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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f37ca61b..0a47248 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -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,
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/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index d61c5d5..7cdf85e 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -49,7 +49,6 @@
     private static final String TAG = "LocalActivityManager";
     private static final boolean localLOGV = false;
 
-    // TODO(b/127877792): try to remove this and use {@code ActivityClientRecord} instead.
     // Internal token for an Activity being managed by LocalActivityManager.
     private static class LocalActivityRecord extends Binder {
         LocalActivityRecord(String _id, Intent _intent) {
@@ -137,7 +136,7 @@
             // startActivity() has not yet been called, so nothing to do.
             return;
         }
-
+        
         if (r.curState == INITIALIZING) {
             // Get the lastNonConfigurationInstance for the activity
             HashMap<String, Object> lastNonConfigurationInstances =
@@ -178,13 +177,12 @@
                 pendingActions = null;
             }
 
-            mActivityThread.handleStartActivity(clientRecord, pendingActions);
+            mActivityThread.handleStartActivity(r, pendingActions);
             r.curState = STARTED;
             
             if (desiredState == RESUMED) {
                 if (localLOGV) Log.v(TAG, r.id + ": resuming");
-                mActivityThread.performResumeActivity(clientRecord, true,
-                        "moveToState-INITIALIZING");
+                mActivityThread.performResumeActivity(r, true, "moveToState-INITIALIZING");
                 r.curState = RESUMED;
             }
             
@@ -196,21 +194,18 @@
             // group's state catches up.
             return;
         }
-
-        final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
-
+        
         switch (r.curState) {
             case CREATED:
                 if (desiredState == STARTED) {
                     if (localLOGV) Log.v(TAG, r.id + ": restarting");
-                    mActivityThread.performRestartActivity(clientRecord, true /* start */);
+                    mActivityThread.performRestartActivity(r, true /* start */);
                     r.curState = STARTED;
                 }
                 if (desiredState == RESUMED) {
                     if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming");
-                    mActivityThread.performRestartActivity(clientRecord, true /* start */);
-                    mActivityThread.performResumeActivity(clientRecord, true,
-                            "moveToState-CREATED");
+                    mActivityThread.performRestartActivity(r, true /* start */);
+                    mActivityThread.performResumeActivity(r, true, "moveToState-CREATED");
                     r.curState = RESUMED;
                 }
                 return;
@@ -219,8 +214,7 @@
                 if (desiredState == RESUMED) {
                     // Need to resume it...
                     if (localLOGV) Log.v(TAG, r.id + ": resuming");
-                    mActivityThread.performResumeActivity(clientRecord, true,
-                            "moveToState-STARTED");
+                    mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
                     r.instanceState = null;
                     r.curState = RESUMED;
                 }
@@ -358,8 +352,7 @@
                     ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
                     intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
                     if (localLOGV) Log.v(TAG, r.id + ": new intent");
-                    final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
-                    mActivityThread.handleNewIntent(clientRecord, intents);
+                    mActivityThread.handleNewIntent(r, intents);
                     r.intent = intent;
                     moveToState(r, mCurState);
                     if (mSingleMode) {
@@ -406,8 +399,7 @@
             performPause(r, finish);
         }
         if (localLOGV) Log.v(TAG, r.id + ": destroying");
-        final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
-        mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+        mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */,
                 false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
         r.activity = null;
         r.window = null;
@@ -672,8 +664,7 @@
         for (int i=0; i<N; i++) {
             LocalActivityRecord r = mActivityArray.get(i);
             if (localLOGV) Log.v(TAG, r.id + ": destroying");
-            final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
-            mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
+            mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */,
                     false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
         }
         mActivities.clear();
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 54f3f10..6c6c04e 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -413,7 +413,7 @@
     public final void disableLocal() {
         synchronized (mLock) {
             mDisabled = true;
-            mCache.clear();
+            clear();
         }
     }
 
@@ -463,7 +463,7 @@
                             cacheName(), mCache.size(),
                             mLastSeenNonce, currentNonce));
                     }
-                    mCache.clear();
+                    clear();
                     mLastSeenNonce = currentNonce;
                     cachedResult = null;
                 }
@@ -728,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;
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fb2120e..9e4ab33 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;
@@ -52,6 +54,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -61,6 +64,7 @@
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /** @hide */
 public class ResourcesManager {
@@ -82,6 +86,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.
      */
@@ -155,20 +165,79 @@
     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 =
@@ -177,7 +246,7 @@
     /**
      * A cache of DisplayId, DisplayAdjustments to Display.
      */
-    private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
+    private final ArrayMap<Pair<Integer, DisplayAdjustments>, SoftReference<Display>>
             mAdjustedDisplays = new ArrayMap<>();
 
     /**
@@ -241,8 +310,7 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public DisplayMetrics getDisplayMetrics() {
-        return getDisplayMetrics(Display.DEFAULT_DISPLAY,
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+        return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
     }
 
     /**
@@ -260,8 +328,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);
@@ -306,25 +374,28 @@
                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
         final Pair<Integer, DisplayAdjustments> key =
                 Pair.create(displayId, displayAdjustmentsCopy);
+        SoftReference<Display> sd;
         synchronized (this) {
-            WeakReference<Display> wd = mAdjustedDisplays.get(key);
-            if (wd != null) {
-                final Display display = wd.get();
-                if (display != null) {
-                    return display;
-                }
-            }
-            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-            if (dm == null) {
-                // may be null early in system startup
-                return null;
-            }
-            final Display display = dm.getCompatibleDisplay(displayId, key.second);
-            if (display != null) {
-                mAdjustedDisplays.put(key, new WeakReference<>(display));
-            }
-            return display;
+            sd = mAdjustedDisplays.get(key);
         }
+        if (sd != null) {
+            final Display display = sd.get();
+            if (display != null) {
+                return display;
+            }
+        }
+        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+        if (dm == null) {
+            // may be null early in system startup
+            return null;
+        }
+        final Display display = dm.getCompatibleDisplay(displayId, key.second);
+        if (display != null) {
+            synchronized (this) {
+                mAdjustedDisplays.put(key, new SoftReference<>(display));
+            }
+        }
+        return display;
     }
 
     /**
@@ -502,7 +573,7 @@
 
             int references = countLiveReferences(mResourceReferences);
             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
-                references += countLiveReferences(activityResources.activityResources);
+                references += activityResources.countLiveReferences();
             }
             pw.println(references);
 
@@ -511,38 +582,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 int generateDisplayId(@NonNull ResourcesKey key) {
+        return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId;
+    }
+
     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
             @Nullable ApkAssetsSupplier apkSupplier) {
-        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
-        daj.setCompatibilityInfo(key.mCompatInfo);
-
         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);
@@ -652,8 +721,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());
 
@@ -667,20 +736,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);
@@ -706,7 +783,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.
      *
@@ -743,7 +820,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();
@@ -759,10 +836,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,
@@ -773,7 +847,9 @@
             }
 
             // Now request an actual Resources object.
-            return createResources(token, key, classLoader, /* apkSupplier */ null);
+            return createResourcesForActivity(token, key,
+                    /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
+                    classLoader, /* apkSupplier */ null);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -781,39 +857,94 @@
 
     /**
      * 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);
+        });
     }
 
     /**
@@ -825,8 +956,8 @@
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                 "ResourcesManager#createApkAssetsSupplierNotLocked");
         try {
-            if (Thread.holdsLock(this)) {
-                Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+            if (DEBUG && Thread.holdsLock(this)) {
+                Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
                     + " is holding mLock", new Throwable());
             }
 
@@ -849,17 +980,36 @@
     /**
      * 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.
-     * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object.
      * @return A Resources object that gets updated when
      *         {@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) {
@@ -873,12 +1023,8 @@
                 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);
         }
     }
 
@@ -899,7 +1045,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.
@@ -909,13 +1058,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,
@@ -927,20 +1077,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);
-            return createResources(activityToken, key, classLoader, assetsSupplier);
+
+            if (overrideDisplayId != null) {
+                rebaseKeyForDisplay(key, overrideDisplayId);
+            }
+
+            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);
         }
@@ -949,24 +1109,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.
@@ -983,6 +1145,8 @@
                 } else {
                     activityResources.overrideConfig.unset();
                 }
+                // Update the Activity's override display id.
+                activityResources.overrideDisplayId = displayId;
 
                 if (DEBUG) {
                     Throwable here = new Throwable();
@@ -1000,24 +1164,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) {
-                        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);
-                        }
+                    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);
                     }
                 }
             }
@@ -1031,9 +1197,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());
@@ -1049,16 +1219,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,
@@ -1090,12 +1277,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;
@@ -1104,10 +1290,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();
 
@@ -1137,11 +1323,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);
         }
 
@@ -1153,22 +1335,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);
     }
@@ -1305,8 +1473,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) {
@@ -1338,10 +1508,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/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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c61426d..98de85d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9751,21 +9751,6 @@
     }
 
     /**
-     * @hide
-     * Return if this user is a system-only user. An admin can manage a device from a system only
-     * user by calling {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}.
-     * @param admin Which device owner this request is associated with.
-     * @return if this user is a system-only user.
-     */
-    public boolean isSystemOnlyUser(@NonNull ComponentName admin) {
-        try {
-            return mService.isSystemOnlyUser(admin);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Called by device owner, or profile owner on organization-owned device, to get the MAC
      * address of the Wi-Fi device.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9c6a274..1c7b617 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -345,7 +345,6 @@
     void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList);
     List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage);
     boolean isManagedProfile(in ComponentName admin);
-    boolean isSystemOnlyUser(in ComponentName admin);
     String getWifiMacAddress(in ComponentName admin);
     void reboot(in ComponentName admin);
 
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 7f43640..fa135b1 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -83,6 +83,8 @@
     private final AppPredictionSessionId mSessionId;
     private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
+    private final IBinder mToken = new Binder();
+
     /**
      * Creates a new Prediction client.
      * <p>
@@ -98,7 +100,7 @@
         mSessionId = new AppPredictionSessionId(
                 context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
         try {
-            mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+            mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create predictor", e);
             e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
index 587e3fd..863fc6f9 100644
--- a/core/java/android/app/prediction/IPredictionManager.aidl
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -29,7 +29,7 @@
 interface IPredictionManager {
 
     void createPredictionSession(in AppPredictionContext context,
-            in AppPredictionSessionId sessionId);
+            in AppPredictionSessionId sessionId, in IBinder token);
 
     void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
 
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 8a4bee9..8b52242 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -20,7 +20,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -33,24 +32,23 @@
  * Activity configuration changed callback.
  * @hide
  */
-public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
+public class ActivityConfigurationChangeItem extends ClientTransactionItem {
 
     private Configuration mConfiguration;
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(r, mConfiguration);
+        client.updatePendingActivityConfiguration(token, mConfiguration);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
-        client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
+        client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -95,7 +93,7 @@
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
-    public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
             new Creator<ActivityConfigurationChangeItem>() {
         public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
             return new ActivityConfigurationChangeItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index cadb660..c9193a9 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -25,7 +25,7 @@
  * Request for lifecycle state that an activity should reach.
  * @hide
  */
-public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
+public abstract class ActivityLifecycleItem extends ClientTransactionItem {
 
     @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = {
             UNDEFINED,
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 87ea3f8..9844de7 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -18,8 +18,7 @@
 
 import static android.app.ActivityThread.DEBUG_ORDER;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ActivityThread;
 import android.app.ClientTransactionHandler;
 import android.app.ResultInfo;
 import android.os.IBinder;
@@ -37,7 +36,7 @@
  * Activity relaunch callback.
  * @hide
  */
-public class ActivityRelaunchItem extends ActivityTransactionItem {
+public class ActivityRelaunchItem extends ClientTransactionItem {
 
     private static final String TAG = "ActivityRelaunchItem";
 
@@ -51,7 +50,7 @@
      * A record that was properly configured for relaunch. Execution will be cancelled if not
      * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
      */
-    private ActivityClientRecord mActivityClientRecord;
+    private ActivityThread.ActivityClientRecord mActivityClientRecord;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -60,7 +59,7 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         if (mActivityClientRecord == null) {
             if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
@@ -74,8 +73,7 @@
     @Override
     public void postExecute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
-        client.reportRelaunch(r, pendingActions);
+        client.reportRelaunch(token, pendingActions);
     }
 
     // ObjectPoolItem implementation
@@ -132,16 +130,16 @@
         mPreserveWindow = in.readBoolean();
     }
 
-    public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<ActivityRelaunchItem> CREATOR =
             new Creator<ActivityRelaunchItem>() {
-        public ActivityRelaunchItem createFromParcel(Parcel in) {
-            return new ActivityRelaunchItem(in);
-        }
+                public ActivityRelaunchItem createFromParcel(Parcel in) {
+                    return new ActivityRelaunchItem(in);
+                }
 
-        public ActivityRelaunchItem[] newArray(int size) {
-            return new ActivityRelaunchItem[size];
-        }
-    };
+                public ActivityRelaunchItem[] newArray(int size) {
+                    return new ActivityRelaunchItem[size];
+                }
+            };
 
     @Override
     public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 47096a8..4e743cac 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -18,11 +18,10 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ResultInfo;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -34,7 +33,7 @@
  * Activity result delivery callback.
  * @hide
  */
-public class ActivityResultItem extends ActivityTransactionItem {
+public class ActivityResultItem extends ClientTransactionItem {
 
     @UnsupportedAppUsage
     private List<ResultInfo> mResultInfoList;
@@ -46,10 +45,10 @@
     }*/
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
-        client.handleSendResult(r, mResultInfoList, "ACTIVITY_RESULT");
+        client.handleSendResult(token, mResultInfoList, "ACTIVITY_RESULT");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -89,7 +88,7 @@
         mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
     }
 
-    public static final @NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
+    public static final @android.annotation.NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
             new Parcelable.Creator<ActivityResultItem>() {
         public ActivityResultItem createFromParcel(Parcel in) {
             return new ActivityResultItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
deleted file mode 100644
index f7d7e9d..0000000
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * An activity-targeting callback message to a client that can be scheduled and executed.
- * It also provides nullity-free version of
- * {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)} for child class
- * to inherit.
- *
- * @see ClientTransaction
- * @see ClientTransactionItem
- * @see com.android.server.wm.ClientLifecycleManager
- * @hide
- */
-public abstract class ActivityTransactionItem extends ClientTransactionItem {
-    @Override
-    public final void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
-
-        execute(client, r, pendingActions);
-    }
-
-    /**
-     * Like {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)},
-     * but take non-null {@link ActivityClientRecord} as a parameter.
-     */
-    @VisibleForTesting(visibility = PACKAGE)
-    public abstract void execute(@NonNull ClientTransactionHandler client,
-            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions);
-
-    @NonNull ActivityClientRecord getActivityClientRecord(
-            @NonNull ClientTransactionHandler client, IBinder token) {
-        final ActivityClientRecord r = client.getActivityClient(token);
-        if (r == null) {
-            throw new IllegalArgumentException("Activity client record must not be null to execute "
-                    + "transaction item");
-        }
-        if (client.getActivity(token) == null) {
-            throw new IllegalArgumentException("Activity must not be null to execute "
-                    + "transaction item");
-        }
-        return r;
-    }
-}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index 1611369..3ee7614 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -18,8 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -40,10 +38,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
-        client.handleDestroyActivity(r, mFinished, mConfigChanges,
+        client.handleDestroyActivity(token, mFinished, mConfigChanges,
                 false /* getNonConfigInstance */, "DestroyActivityItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -94,7 +92,7 @@
         mConfigChanges = in.readInt();
     }
 
-    public static final @NonNull Creator<DestroyActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<DestroyActivityItem> CREATOR =
             new Creator<DestroyActivityItem>() {
         public DestroyActivityItem createFromParcel(Parcel in) {
             return new DestroyActivityItem(in);
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index b7e81a5..b2a1276 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -16,20 +16,20 @@
 
 package android.app.servertransaction;
 
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
+import android.os.IBinder;
 import android.os.Parcel;
 
 /**
  * Request an activity to enter picture-in-picture mode.
  * @hide
  */
-public final class EnterPipRequestedItem extends ActivityTransactionItem {
+public final class EnterPipRequestedItem extends ClientTransactionItem {
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
-        client.handlePictureInPictureRequested(r);
+        client.handlePictureInPictureRequested(token);
     }
 
     // ObjectPoolItem implementation
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 77457af..2e7b626 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -18,7 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.ProfilerInfo;
@@ -164,7 +163,7 @@
                 in.readTypedObject(FixedRotationAdjustments.CREATOR));
     }
 
-    public static final @NonNull Creator<LaunchActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
             new Creator<LaunchActivityItem>() {
         public LaunchActivityItem createFromParcel(Parcel in) {
             return new LaunchActivityItem(in);
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 32de53f..9a457a3 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -19,7 +19,6 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
@@ -32,24 +31,23 @@
  * Activity move to a different display message.
  * @hide
  */
-public class MoveToDisplayItem extends ActivityTransactionItem {
+public class MoveToDisplayItem extends ClientTransactionItem {
 
     private int mTargetDisplayId;
     private Configuration mConfiguration;
 
     @Override
     public void preExecute(ClientTransactionHandler client, IBinder token) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(r, mConfiguration);
+        client.updatePendingActivityConfiguration(token, mConfiguration);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
-        client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
+        client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -98,8 +96,7 @@
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
-    public static final @NonNull Creator<MoveToDisplayItem> CREATOR =
-            new Creator<MoveToDisplayItem>() {
+    public static final @android.annotation.NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
         public MoveToDisplayItem createFromParcel(Parcel in) {
             return new MoveToDisplayItem(in);
         }
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index b4e2a7b..6a4996d 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -19,10 +19,9 @@
 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -36,7 +35,7 @@
  * New intent message.
  * @hide
  */
-public class NewIntentItem extends ActivityTransactionItem {
+public class NewIntentItem extends ClientTransactionItem {
 
     @UnsupportedAppUsage
     private List<ReferrerIntent> mIntents;
@@ -48,10 +47,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
-        client.handleNewIntent(r, mIntents);
+        client.handleNewIntent(token, mIntents);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -95,7 +94,7 @@
         mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
     }
 
-    public static final @NonNull Parcelable.Creator<NewIntentItem> CREATOR =
+    public static final @android.annotation.NonNull Parcelable.Creator<NewIntentItem> CREATOR =
             new Parcelable.Creator<NewIntentItem>() {
         public NewIntentItem createFromParcel(Parcel in) {
             return new NewIntentItem(in);
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index cb154e9..f65c843 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -18,9 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -41,10 +40,10 @@
     private boolean mDontReport;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions,
+        client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,
                 "PAUSE_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -131,7 +130,7 @@
         mDontReport = in.readBoolean();
     }
 
-    public static final @NonNull Creator<PauseActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<PauseActivityItem> CREATOR =
             new Creator<PauseActivityItem>() {
         public PauseActivityItem createFromParcel(Parcel in) {
             return new PauseActivityItem(in);
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d2a156c..905076b 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -18,10 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -48,10 +46,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
-        client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
+        client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
                 "RESUME_ACTIVITY");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -130,7 +128,7 @@
         mIsForward = in.readBoolean();
     }
 
-    public static final @NonNull Creator<ResumeActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<ResumeActivityItem> CREATOR =
             new Creator<ResumeActivityItem>() {
         public ResumeActivityItem createFromParcel(Parcel in) {
             return new ResumeActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index ae0bd24..4fbe02b 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -18,9 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Trace;
 
@@ -33,10 +32,10 @@
     private static final String TAG = "StartActivityItem";
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
-        client.handleStartActivity(r, pendingActions);
+        client.handleStartActivity(token, pendingActions);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -80,7 +79,7 @@
         // Empty
     }
 
-    public static final @NonNull Creator<StartActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
             new Creator<StartActivityItem>() {
                 public StartActivityItem createFromParcel(Parcel in) {
                     return new StartActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 7708104..8668bd4 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -18,8 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -36,10 +34,10 @@
     private int mConfigChanges;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(r, mConfigChanges, pendingActions,
+        client.handleStopActivity(token, mConfigChanges, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -95,7 +93,7 @@
         mConfigChanges = in.readInt();
     }
 
-    public static final @NonNull Creator<StopActivityItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<StopActivityItem> CREATOR =
             new Creator<StopActivityItem>() {
         public StopActivityItem createFromParcel(Parcel in) {
             return new StopActivityItem(in);
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index 345c1dd..c7e4c36 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -17,9 +17,7 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
-import android.annotation.NonNull;
 import android.app.ActivityTaskManager;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -30,15 +28,15 @@
  * Top resumed activity changed callback.
  * @hide
  */
-public class TopResumedActivityChangeItem extends ActivityTransactionItem {
+public class TopResumedActivityChangeItem extends ClientTransactionItem {
 
     private boolean mOnTop;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
+    public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
-        client.handleTopResumedActivityChanged(r, mOnTop, "topResumedActivityChangeItem");
+        client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -99,16 +97,16 @@
         mOnTop = in.readBoolean();
     }
 
-    public static final @NonNull Creator<TopResumedActivityChangeItem> CREATOR =
+    public static final @android.annotation.NonNull Creator<TopResumedActivityChangeItem> CREATOR =
             new Creator<TopResumedActivityChangeItem>() {
-        public TopResumedActivityChangeItem createFromParcel(Parcel in) {
-            return new TopResumedActivityChangeItem(in);
-        }
+                public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+                    return new TopResumedActivityChangeItem(in);
+                }
 
-        public TopResumedActivityChangeItem[] newArray(int size) {
-            return new TopResumedActivityChangeItem[size];
-        }
-    };
+                public TopResumedActivityChangeItem[] newArray(int size) {
+                    return new TopResumedActivityChangeItem[size];
+                }
+            };
 
     @Override
     public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 3dcf2cb..17fcda5 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,29 +218,29 @@
                             null /* customIntent */);
                     break;
                 case ON_START:
-                    mTransactionHandler.handleStartActivity(r, mPendingActions);
+                    mTransactionHandler.handleStartActivity(r.token, mPendingActions);
                     break;
                 case ON_RESUME:
-                    mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
+                    mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
                             r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
                     break;
                 case ON_PAUSE:
-                    mTransactionHandler.handlePauseActivity(r, false /* finished */,
+                    mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
                             false /* userLeaving */, 0 /* configChanges */, mPendingActions,
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
+                    mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
                             mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
-                    mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
+                    mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
                             0 /* configChanges */, false /* getNonConfigInstance */,
                             "performLifecycleSequence. cycling to:" + path.get(size - 1));
                     break;
                 case ON_RESTART:
-                    mTransactionHandler.performRestartActivity(r, false /* start */);
+                    mTransactionHandler.performRestartActivity(r.token, false /* start */);
                     break;
                 default:
                     throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
index af77fe0..6d0fe72 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
@@ -20,5 +20,5 @@
 
 /** {@hide} */
 oneway interface ITimeZoneConfigurationListener {
-    void onChange(in TimeZoneConfiguration configuration);
+    void onChange();
 }
\ No newline at end of file
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 6e93af6..4f7e1f6 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -32,17 +32,15 @@
  * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
  * for more complete documentation.
  *
- *
  * {@hide}
  */
 interface ITimeZoneDetectorService {
   TimeZoneCapabilities getCapabilities();
-
-  TimeZoneConfiguration getConfiguration();
-  boolean updateConfiguration(in TimeZoneConfiguration configuration);
   void addConfigurationListener(ITimeZoneConfigurationListener listener);
   void removeConfigurationListener(ITimeZoneConfigurationListener listener);
 
+  boolean updateConfiguration(in TimeZoneConfiguration configuration);
+
   boolean suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
   void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
new file mode 100644
index 0000000..46f2319
--- /dev/null
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.timezonedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index cc0af3f..09fffe9 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -16,9 +16,12 @@
 
 package android.app.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.UserIdInt;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,9 +41,9 @@
  *
  * <p>Actions have associated methods, see the documentation for each action for details.
  *
- * <p>For configuration capabilities, the associated current configuration value can be retrieved
- * using {@link TimeZoneDetector#getConfiguration()} and may be changed using
- * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)}.
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link #getConfiguration()} and may be changed using {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
  *
  * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
  *
@@ -60,7 +63,8 @@
     public static final int CAPABILITY_NOT_SUPPORTED = 10;
 
     /**
-     * Indicates that a capability is supported on this device, but not allowed for the user.
+     * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+     * if the capability relates to the ability to modify settings the user is not able to.
      * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
      * device policy. Depending on the capability, this could mean the associated UI
      * should be hidden, or displayed but disabled.
@@ -68,9 +72,11 @@
     public static final int CAPABILITY_NOT_ALLOWED = 20;
 
     /**
-     * Indicates that a capability is possessed but not applicable, e.g. if it is configuration,
-     * the current configuration or device state renders it irrelevant. The associated UI may be
-     * hidden, disabled, or left visible (but ineffective) depending on requirements.
+     * Indicates that a capability is possessed but not currently applicable, e.g. if the
+     * capability relates to the ability to modify settings, the user has the ability to modify
+     * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+     * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+     * ineffective) depending on requirements.
      */
     public static final int CAPABILITY_NOT_APPLICABLE = 30;
 
@@ -89,13 +95,13 @@
             };
 
 
-    private final @UserIdInt int mUserId;
+    @NonNull private final TimeZoneConfiguration mConfiguration;
     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.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
         this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
         this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
         this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
@@ -103,7 +109,8 @@
 
     @NonNull
     private static TimeZoneCapabilities createFromParcel(Parcel in) {
-        return new TimeZoneCapabilities.Builder(in.readInt())
+        return new TimeZoneCapabilities.Builder()
+                .setConfiguration(in.readParcelable(null))
                 .setConfigureAutoDetectionEnabled(in.readInt())
                 .setConfigureGeoDetectionEnabled(in.readInt())
                 .setSuggestManualTimeZone(in.readInt())
@@ -112,21 +119,24 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mUserId);
+        dest.writeParcelable(mConfiguration, flags);
         dest.writeInt(mConfigureAutoDetectionEnabled);
         dest.writeInt(mConfigureGeoDetectionEnabled);
         dest.writeInt(mSuggestManualTimeZone);
     }
 
-    /** Returns the user ID the capabilities are for. */
-    public @UserIdInt int getUserId() {
-        return mUserId;
+    /**
+     * Returns the user's time zone behavior configuration.
+     */
+    public @NonNull TimeZoneConfiguration getConfiguration() {
+        return mConfiguration;
     }
 
     /**
-     * Returns the user's capability state for controlling whether automatic time zone detection is
-     * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
-     * TimeZoneConfiguration#isAutoDetectionEnabled()}.
+     * Returns the capability state associated with the user's ability to modify the automatic time
+     * zone detection setting. The setting can be updated via {@link
+     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+     * #getConfiguration()}.
      */
     @CapabilityState
     public int getConfigureAutoDetectionEnabled() {
@@ -134,9 +144,10 @@
     }
 
     /**
-     * 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()}.
+     * Returns the capability state associated with the user's ability to modify the geolocation
+     * detection setting. The setting can be updated via {@link
+     * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
+     * #getConfiguration()}.
      */
     @CapabilityState
     public int getConfigureGeoDetectionEnabled() {
@@ -144,8 +155,8 @@
     }
 
     /**
-     * Returns the user's capability state for manually setting the time zone on a device via
-     * {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+     * Returns the capability state associated with the user's ability to manually set the time zone
+     * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
      *
      * <p>The suggestion will be ignored in all cases unless the value is {@link
      * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
@@ -155,6 +166,38 @@
         return mSuggestManualTimeZone;
     }
 
+    /**
+     * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
+     * {@code requestedChanges}, if the current capabilities allow. The new configuration is
+     * returned and the capabilities are left unchanged. If the capabilities do not permit one or
+     * more of the changes then {@code null} is returned.
+     */
+    @Nullable
+    public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
+        if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
+            throw new IllegalArgumentException("User does not match:"
+                    + " this=" + mConfiguration + ", other=" + requestedChanges);
+        }
+
+        TimeZoneConfiguration.Builder newConfigBuilder =
+                new TimeZoneConfiguration.Builder(mConfiguration);
+        if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
+            if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
+        }
+
+        if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
+            if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
+        }
+
+        return newConfigBuilder.build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -169,7 +212,7 @@
             return false;
         }
         TimeZoneCapabilities that = (TimeZoneCapabilities) o;
-        return mUserId == that.mUserId
+        return Objects.equals(mConfiguration, that.mConfiguration)
                 && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
                 && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
                 && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
@@ -177,7 +220,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUserId,
+        return Objects.hash(mConfiguration,
                 mConfigureAutoDetectionEnabled,
                 mConfigureGeoDetectionEnabled,
                 mSuggestManualTimeZone);
@@ -186,7 +229,7 @@
     @Override
     public String toString() {
         return "TimeZoneDetectorCapabilities{"
-                + "mUserId=" + mUserId
+                + "mConfiguration=" + mConfiguration
                 + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
                 + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
                 + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
@@ -196,16 +239,18 @@
     /** @hide */
     public static class Builder {
 
-        private final @UserIdInt int mUserId;
+        private TimeZoneConfiguration mConfiguration;
         private @CapabilityState int mConfigureAutoDetectionEnabled;
         private @CapabilityState int mConfigureGeoDetectionEnabled;
         private @CapabilityState int mSuggestManualTimeZone;
 
-        /**
-         * Creates a new Builder with no properties set.
-         */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
+        /** Sets the user-visible configuration settings. */
+        public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
+            if (!configuration.isComplete()) {
+                throw new IllegalArgumentException(configuration + " is not complete");
+            }
+            this.mConfiguration = configuration;
+            return this;
         }
 
         /** Sets the state for the automatic time zone detection enabled config. */
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 6f84ee2..95db0a2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.StringDef;
+import android.annotation.UserIdInt;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,21 +28,20 @@
 import java.util.Objects;
 
 /**
- * Configuration that controls the behavior of the time zone detector associated with a specific
- * user.
+ * User visible settings that control the behavior of the time zone detector / manual time zone
+ * entry.
  *
- * <p>Configuration consists of a set of known properties. When reading configuration via
- * {@link TimeZoneDetector#getConfiguration()} values for all known properties will be provided. In
- * some cases, such as when the configuration relies on optional hardware, the values may be
- * meaningless / defaulted to safe values.
+ * <p>When reading the configuration, values for all settings will be provided. In some cases, such
+ * as when the device behavior relies on optional hardware / OEM configuration, or the value of
+ * several settings, the device behavior may not be directly affected by the setting value.
  *
- * <p>Configuration properties can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those values will not be
- * changed. Not all configuration properties can be modified by all users. See {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities}.
+ * <p>Settings can be left absent when updating configuration via {@link
+ * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * changed. Not all configuration settings can be modified by all users: see {@link
+ * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
  *
- * <p>See {@link #isComplete()} to tell if all known properties are present, and {@link
- * #hasProperty(String)} with {@code PROPERTY_} constants for testing individual properties.
+ * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
+ * of individual settings.
  *
  * @hide
  */
@@ -59,80 +59,82 @@
             };
 
     /** All configuration properties */
-    @StringDef(PROPERTY_AUTO_DETECTION_ENABLED)
+    @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
     @Retention(RetentionPolicy.SOURCE)
-    @interface Property {}
+    @interface Setting {}
 
     /** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
-    @Property
-    public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+    @Setting
+    public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
     /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
-    @Property
-    public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+    @Setting
+    public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
 
-    private final Bundle mBundle;
+    private final @UserIdInt int mUserId;
+    @NonNull private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
-        this.mBundle = builder.mBundle;
+        this.mUserId = builder.mUserId;
+        this.mBundle = Objects.requireNonNull(builder.mBundle);
     }
 
     private static TimeZoneConfiguration createFromParcel(Parcel in) {
-        return new TimeZoneConfiguration.Builder()
+        return new TimeZoneConfiguration.Builder(in.readInt())
                 .setPropertyBundleInternal(in.readBundle())
                 .build();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mUserId);
         dest.writeBundle(mBundle);
     }
 
-    /** Returns {@code true} if all known properties are set. */
-    public boolean isComplete() {
-        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
-                && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
+    /** Returns the ID of the user this configuration is associated with. */
+    public @UserIdInt int getUserId() {
+        return mUserId;
     }
 
-    /** Returns true if the specified property is set. */
-    public boolean hasProperty(@Property String property) {
-        return mBundle.containsKey(property);
+    /** Returns {@code true} if all known settings are present. */
+    public boolean isComplete() {
+        return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
+                && hasSetting(SETTING_GEO_DETECTION_ENABLED);
+    }
+
+    /** Returns true if the specified setting is set. */
+    public boolean hasSetting(@Setting String setting) {
+        return mBundle.containsKey(setting);
     }
 
     /**
-     * Returns the value of the {@link #PROPERTY_AUTO_DETECTION_ENABLED} property. This
+     * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This
      * controls whether a device will attempt to determine the time zone automatically using
-     * contextual information.
+     * contextual information if the device supports auto detection.
      *
-     * @throws IllegalStateException if the field has not been set
+     * <p>This setting is global and can be updated by some users.
+     *
+     * @throws IllegalStateException if the setting has not been set
      */
     public boolean isAutoDetectionEnabled() {
-        if (!mBundle.containsKey(PROPERTY_AUTO_DETECTION_ENABLED)) {
-            throw new IllegalStateException(PROPERTY_AUTO_DETECTION_ENABLED + " is not set");
-        }
-        return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
+        enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
+        return mBundle.getBoolean(SETTING_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.
+     * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
+     * controls whether a device can use geolocation to determine time zone. Only used when
+     * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
+     * location to be used.
      *
-     * @throws IllegalStateException if the field has not been set
+     * <p>This setting is user-scoped and can be updated by some users.
+     * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
+     *
+     * @throws IllegalStateException if the setting 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();
+        enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
+        return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
     }
 
     @Override
@@ -149,43 +151,61 @@
             return false;
         }
         TimeZoneConfiguration that = (TimeZoneConfiguration) o;
-        return mBundle.kindofEquals(that.mBundle);
+        return mUserId == that.mUserId
+                && mBundle.kindofEquals(that.mBundle);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mBundle);
+        return Objects.hash(mUserId, mBundle);
     }
 
     @Override
     public String toString() {
         return "TimeZoneDetectorConfiguration{"
+                + "mUserId=" + mUserId
                 + "mBundle=" + mBundle
                 + '}';
     }
 
+    private void enforceSettingPresent(@Setting String setting) {
+        if (!mBundle.containsKey(setting)) {
+            throw new IllegalStateException(setting + " is not set");
+        }
+    }
+
     /** @hide */
     public static class Builder {
 
-        private Bundle mBundle = new Bundle();
+        private final @UserIdInt int mUserId;
+        private final Bundle mBundle = new Bundle();
 
         /**
-         * Creates a new Builder with no properties set.
+         * Creates a new Builder for a userId with no settings held.
          */
-        public Builder() {}
+        public Builder(@UserIdInt int userId) {
+            mUserId = userId;
+        }
 
         /**
-         * Creates a new Builder by copying properties from an existing instance.
+         * Creates a new Builder by copying the user ID and settings from an existing instance.
          */
         public Builder(TimeZoneConfiguration toCopy) {
+            this.mUserId = toCopy.mUserId;
             mergeProperties(toCopy);
         }
 
         /**
-         * Merges {@code other} properties into this instances, replacing existing values in this
-         * where the properties appear in both.
+         * Merges {@code other} settings into this instances, replacing existing values in this
+         * where the settings appear in both.
          */
         public Builder mergeProperties(TimeZoneConfiguration other) {
+            if (mUserId != other.mUserId) {
+                throw new IllegalArgumentException(
+                        "Cannot merge configurations for different user IDs."
+                                + " this.mUserId=" + this.mUserId
+                                + ", other.mUserId=" + other.mUserId);
+            }
             this.mBundle.putAll(other.mBundle);
             return this;
         }
@@ -195,15 +215,19 @@
             return this;
         }
 
-        /** Sets the desired state of the automatic time zone detection property. */
+        /**
+         * Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
+         */
         public Builder setAutoDetectionEnabled(boolean enabled) {
-            this.mBundle.putBoolean(PROPERTY_AUTO_DETECTION_ENABLED, enabled);
+            this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
             return this;
         }
 
-        /** Sets the desired state of the geolocation time zone detection enabled property. */
+        /**
+         * Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
+         */
         public Builder setGeoDetectionEnabled(boolean enabled) {
-            this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+            this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
             return this;
         }
 
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 7885613..2b1cbf2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -37,37 +37,29 @@
     TimeZoneCapabilities getCapabilities();
 
     /**
-     * Returns the current user's complete time zone configuration. See {@link
-     * TimeZoneConfiguration}.
-     */
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @NonNull
-    TimeZoneConfiguration getConfiguration();
-
-    /**
      * Modifies the time zone detection configuration.
      *
-     * <p>Configuration properties vary in scope: some may be device-wide, others may be specific to
-     * the current user.
+     * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+     * specific to the current user.
      *
-     * <p>The ability to modify configuration properties can be subject to restrictions. For
+     * <p>The ability to modify configuration settings can be subject to restrictions. For
      * example, they may be determined by device hardware, general policy (i.e. only the primary
-     * user can set them), or by a managed device policy. See {@link #getCapabilities()} to obtain
+     * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
      * information at runtime about the user's capabilities.
      *
-     * <p>Attempts to set configuration with capabilities that are {@link
+     * <p>Attempts to modify configuration settings with capabilities that are {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
-     * will be returned. Setting configuration with capabilities that are {@link
+     * will be returned. Modifying configuration settings with capabilities that are {@link
      * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
      * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
      * TimeZoneCapabilities} for further details.
      *
-     * <p>If the configuration is not "complete", then only the specified properties will be
-     * updated (where the user's capabilities allow) and other settings will be left unchanged. See
-     * {@link TimeZoneConfiguration#isComplete()}.
+     * <p>If the supplied configuration only has some values set, then only the specified settings
+     * will be updated (where the user's capabilities allow) and other settings will be left
+     * unchanged.
      *
-     * @return {@code true} if all the configuration properties specified have been set to the
+     * @return {@code true} if all the configuration settings specified have been set to the
      *   new values, {@code false} if none have
      */
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -76,14 +68,20 @@
     /**
      * An interface that can be used to listen for changes to the time zone detector configuration.
      */
+    @FunctionalInterface
     interface TimeZoneConfigurationListener {
-        /** Called when the configuration changes. There are no guarantees about the thread used. */
-        void onChange(@NonNull TimeZoneConfiguration configuration);
+        /**
+         * Called when something about the time zone configuration on the device has changed.
+         * This could be because the current user has changed, one of the device's relevant settings
+         * has changed, or something that could affect a user's capabilities has changed.
+         * There are no guarantees about the thread used.
+         */
+        void onChange();
     }
 
     /**
-     * Registers a listener that will be informed when the configuration changes. The complete
-     * configuration is passed to the listener, not just the properties that have changed.
+     * Registers a listener that will be informed when something about the time zone configuration
+     * changes.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 0770aff..4c69732 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -57,19 +57,6 @@
     }
 
     @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration() {
-        if (DEBUG) {
-            Log.d(TAG, "getConfiguration called");
-        }
-        try {
-            return mITimeZoneDetectorService.getConfiguration();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
     public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
         if (DEBUG) {
             Log.d(TAG, "updateConfiguration called: " + configuration);
@@ -94,8 +81,8 @@
                 ITimeZoneConfigurationListener iListener =
                         new ITimeZoneConfigurationListener.Stub() {
                     @Override
-                    public void onChange(@NonNull TimeZoneConfiguration configuration) {
-                        notifyConfigurationListeners(configuration);
+                    public void onChange() {
+                        notifyConfigurationListeners();
                     }
                 };
                 mConfigurationReceiver = iListener;
@@ -116,14 +103,14 @@
         }
     }
 
-    private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) {
+    private void notifyConfigurationListeners() {
         final ArraySet<TimeZoneConfigurationListener> configurationListeners;
         synchronized (this) {
             configurationListeners = new ArraySet<>(mConfigurationListeners);
         }
         int size = configurationListeners.size();
         for (int i = 0; i < size; i++) {
-            configurationListeners.valueAt(i).onChange(configuration);
+            configurationListeners.valueAt(i).onChange();
         }
     }
 
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/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/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/core/java/android/content/pm/FileChecksum.aidl
similarity index 76%
copy from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
copy to core/java/android/content/pm/FileChecksum.aidl
index 9d05843..109f211 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/core/java/android/content/pm/FileChecksum.aidl
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings
+package android.content.pm;
 
-import android.content.ContentResolver
+parcelable FileChecksum;
 
-interface CurrentUserContentResolverProvider {
-
-    val currentUserContentResolver: ContentResolver
-}
\ No newline at end of file
diff --git a/core/java/android/content/pm/FileChecksum.java b/core/java/android/content/pm/FileChecksum.java
new file mode 100644
index 0000000..55430c2
--- /dev/null
+++ b/core/java/android/content/pm/FileChecksum.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * A typed checksum.
+ *
+ * @see PackageManager#getChecksums(String, boolean, int, List, IntentSender)
+ */
+@DataClass(genHiddenConstructor = true)
+public final class FileChecksum implements Parcelable {
+    /**
+     * Checksum for which split. Null indicates base.apk.
+     */
+    private final @Nullable String mSplitName;
+    /**
+     * Checksum kind.
+     */
+    private final @PackageManager.FileChecksumKind int mKind;
+    /**
+     * Checksum value.
+     */
+    private final @NonNull byte[] mValue;
+    /**
+     * For Installer-provided checksums, certificate of the Installer/AppStore.
+     */
+    private final @Nullable byte[] mSourceCertificate;
+
+    /**
+     * Constructor, internal use only
+     *
+     * @hide
+     */
+    public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
+            @NonNull byte[] value) {
+        this(splitName, kind, value, (byte[]) null);
+    }
+
+    /**
+     * Constructor, internal use only
+     *
+     * @hide
+     */
+    public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
+            @NonNull byte[] value, @Nullable Certificate sourceCertificate)
+            throws CertificateEncodingException {
+        this(splitName, kind, value,
+                (sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
+    }
+
+    /**
+     * Certificate of the source of this checksum.
+     * @throws CertificateException in case when certificate can't be re-created from serialized
+     * data.
+     */
+    public @Nullable Certificate getSourceCertificate() throws CertificateException {
+        if (mSourceCertificate == null) {
+            return null;
+        }
+        final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        final InputStream is = new ByteArrayInputStream(mSourceCertificate);
+        final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+        return cert;
+    }
+
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/FileChecksum.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new FileChecksum.
+     *
+     * @param splitName
+     *   Checksum for which split. Null indicates base.apk.
+     * @param kind
+     *   Checksum kind.
+     * @param value
+     *   Checksum value.
+     * @param sourceCertificate
+     *   For Installer-provided checksums, certificate of the Installer/AppStore.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public FileChecksum(
+            @Nullable String splitName,
+            @PackageManager.FileChecksumKind int kind,
+            @NonNull byte[] value,
+            @Nullable byte[] sourceCertificate) {
+        this.mSplitName = splitName;
+        this.mKind = kind;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.FileChecksumKind.class, null, mKind);
+        this.mValue = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mValue);
+        this.mSourceCertificate = sourceCertificate;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Checksum for which split. Null indicates base.apk.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getSplitName() {
+        return mSplitName;
+    }
+
+    /**
+     * Checksum kind.
+     */
+    @DataClass.Generated.Member
+    public @PackageManager.FileChecksumKind int getKind() {
+        return mKind;
+    }
+
+    /**
+     * Checksum value.
+     */
+    @DataClass.Generated.Member
+    public @NonNull byte[] getValue() {
+        return mValue;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mSplitName != null) flg |= 0x1;
+        if (mSourceCertificate != null) flg |= 0x8;
+        dest.writeByte(flg);
+        if (mSplitName != null) dest.writeString(mSplitName);
+        dest.writeInt(mKind);
+        dest.writeByteArray(mValue);
+        if (mSourceCertificate != null) dest.writeByteArray(mSourceCertificate);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ FileChecksum(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String splitName = (flg & 0x1) == 0 ? null : in.readString();
+        int kind = in.readInt();
+        byte[] value = in.createByteArray();
+        byte[] sourceCertificate = (flg & 0x8) == 0 ? null : in.createByteArray();
+
+        this.mSplitName = splitName;
+        this.mKind = kind;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.FileChecksumKind.class, null, mKind);
+        this.mValue = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mValue);
+        this.mSourceCertificate = sourceCertificate;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<FileChecksum> CREATOR
+            = new Parcelable.Creator<FileChecksum>() {
+        @Override
+        public FileChecksum[] newArray(int size) {
+            return new FileChecksum[size];
+        }
+
+        @Override
+        public FileChecksum createFromParcel(@NonNull Parcel in) {
+            return new FileChecksum(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1598322801861L,
+            codegenVersion = "1.0.15",
+            sourceFile = "frameworks/base/core/java/android/content/pm/FileChecksum.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.content.pm.PackageManager.FileChecksumKind int mKind\nprivate final @android.annotation.NonNull byte[] mValue\nprivate final @android.annotation.Nullable byte[] mSourceCertificate\npublic @android.annotation.Nullable java.security.cert.Certificate getSourceCertificate()\nclass FileChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6a8dd81..1f8cee2 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -743,6 +743,8 @@
 
     void notifyPackagesReplacedReceived(in String[] packages);
 
+    void getChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId);
+
     //------------------------------------------------------------------------
     //
     // The following binder interfaces have been moved to IPermissionManager
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 bed7b26..b7c3289 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2076,7 +2076,7 @@
                 STAGED_SESSION_VERIFICATION_FAILED,
                 STAGED_SESSION_ACTIVATION_FAILED,
                 STAGED_SESSION_UNKNOWN,
-                STAGED_SESSION_OTHER_ERROR})
+                STAGED_SESSION_CONFLICT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StagedSessionErrorCode{}
         /**
@@ -2103,10 +2103,10 @@
         public static final int STAGED_SESSION_UNKNOWN = 3;
 
         /**
-         * Constant indicating that a known error occurred while processing this staged session, but
-         * the error could not be matched to other categories.
+         * 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_OTHER_ERROR = 4;
+        public static final int STAGED_SESSION_CONFLICT = 4;
 
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7b2955d..da8d15a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -79,8 +79,11 @@
 import dalvik.system.VMRuntime;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -3305,6 +3308,13 @@
     public static final String EXTRA_FAILURE_EXISTING_PERMISSION
             = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION";
 
+    /**
+     * Extra field name for the ID of a package pending verification. Passed to
+     * a package verifier and is used to call back to
+     * @see #getChecksums
+     */
+    public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
+
    /**
     * Permission flag: The permission is set in its current state
     * by the user and apps can still request it at runtime.
@@ -7842,6 +7852,114 @@
     }
 
     /**
+     * Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
+     * <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
+     * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
+     *
+     * @see #getChecksums
+     */
+    public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
+
+    /**
+     * MD5 hash computed over all file bytes.
+     *
+     * @see #getChecksums
+     */
+    public static final int WHOLE_MD5 = 0x00000002;
+
+    /**
+     * SHA1 hash computed over all file bytes.
+     *
+     * @see #getChecksums
+     */
+    public static final int WHOLE_SHA1 = 0x00000004;
+
+    /**
+     * SHA256 hash computed over all file bytes.
+     *
+     * @see #getChecksums
+     */
+    public static final int WHOLE_SHA256 = 0x00000008;
+
+    /**
+     * SHA512 hash computed over all file bytes.
+     *
+     * @see #getChecksums
+     */
+    public static final int WHOLE_SHA512 = 0x00000010;
+
+    /**
+     * Root SHA256 hash of a 1M Merkle tree computed over protected content.
+     * Excludes signing block.
+     * <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
+     *
+     * @see #getChecksums
+     */
+    public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
+
+    /**
+     * Root SHA512 hash of a 1M Merkle tree computed over protected content.
+     * Excludes signing block.
+     * <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
+     *
+     * @see #getChecksums
+     */
+    public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"WHOLE_", "PARTIAL_"}, value = {
+            WHOLE_MERKLE_ROOT_4K_SHA256,
+            WHOLE_MD5,
+            WHOLE_SHA1,
+            WHOLE_SHA256,
+            WHOLE_SHA512,
+            PARTIAL_MERKLE_ROOT_1M_SHA256,
+            PARTIAL_MERKLE_ROOT_1M_SHA512,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FileChecksumKind {}
+
+    /**
+     * Trust any Installer to provide checksums for the package.
+     * @see #getChecksums
+     */
+    public static final @Nullable List<Certificate> TRUST_ALL = null;
+
+    /**
+     * Don't trust any Installer to provide checksums for the package.
+     * This effectively disables optimized Installer-enforced checksums.
+     * @see #getChecksums
+     */
+    public static final @NonNull List<Certificate> TRUST_NONE = Collections.emptyList();
+
+    /**
+     * Returns the checksums for APKs within a package.
+     *
+     * By default returns all readily available checksums:
+     * - enforced by platform,
+     * - enforced by installer.
+     * If caller needs a specific checksum kind, they can specify it as required.
+     *
+     * @param packageName whose checksums to return.
+     * @param includeSplits whether to include checksums for non-base splits.
+     * @param required explicitly request the checksum kinds. Will incur significant
+     *                 CPU/memory/disk usage.
+     * @param trustedInstallers for checksums enforced by Installer, which ones to be trusted.
+     *                          {@link #TRUST_ALL} will return checksums from any Installer,
+     *                          {@link #TRUST_NONE} disables optimized Installer-enforced checksums.
+     * @param statusReceiver called once when the results are available as
+     *                       {@link #EXTRA_CHECKSUMS} of type FileChecksum[].
+     * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
+     * @throws NameNotFoundException if a package with the given name cannot be found on the system.
+     */
+    public void getChecksums(@NonNull String packageName, boolean includeSplits,
+            @FileChecksumKind int required, @Nullable List<Certificate> trustedInstallers,
+            @NonNull IntentSender statusReceiver)
+            throws CertificateEncodingException, IOException, NameNotFoundException {
+        throw new UnsupportedOperationException("getChecksums not implemented in subclass");
+    }
+
+    /**
      * @return the default text classifier package name, or null if there's none.
      *
      * @hide
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/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index bd909c7..192470e 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -43,11 +43,11 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
-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;
@@ -793,7 +793,7 @@
 
     @VisibleForTesting
     protected List<UserInfo> getUsers() {
-        return UserManager.get(mContext).getUsers(true);
+        return UserManager.get(mContext).getAliveUsers();
     }
 
     @VisibleForTesting
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/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/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/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 4f949cf..1b114d3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -143,14 +143,9 @@
         }
 
         @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
@@ -417,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
@@ -458,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();
             }
@@ -470,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();
             }
@@ -1083,18 +1101,18 @@
     }
 
     /**
-     * Callback structure provided to {@link #generateChallenge(GenerateChallengeCallback)}.
+     * Callback structure provided to {@link #generateChallenge(int, GenerateChallengeCallback)}.
      * @hide
      */
     public interface GenerateChallengeCallback {
         /**
          * Invoked when a challenge has been generated.
          */
-        void onGenerateChallengeResult(long challenge);
+        void onGenerateChallengeResult(int sensorId, long challenge);
 
         /**
          * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
-         * {@link #generateChallenge(GenerateChallengeCallback)}, but
+         * {@link #generateChallenge(int, GenerateChallengeCallback)}, but
          */
         default void onChallengeInterrupted(int sensorId) {}
 
@@ -1104,9 +1122,6 @@
         default void onChallengeInterruptFinished(int sensorId) {}
     }
 
-    private abstract static class InternalGenerateChallengeCallback
-            implements GenerateChallengeCallback {}
-
     private class OnEnrollCancelListener implements OnCancelListener {
         @Override
         public void onCancel() {
@@ -1178,7 +1193,7 @@
                     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 */,
@@ -1211,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) {
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 108f4f6..bd4d3a0 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -31,7 +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 71598eb..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;
@@ -378,12 +379,9 @@
      * @hide
      */
     public interface GenerateChallengeCallback {
-        void onChallengeGenerated(long challenge);
+        void onChallengeGenerated(int sensorId, long challenge);
     }
 
-    private abstract static class InternalGenerateChallengeCallback
-            implements GenerateChallengeCallback {}
-
     /**
      * Request authentication of a crypto object. This call warms up the fingerprint hardware
      * and starts scanning for a fingerprint. It terminates when
@@ -593,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
      */
@@ -616,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
@@ -901,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 */,
@@ -989,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) {
@@ -1178,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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b0d4497..fecfd3c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2954,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/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/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/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/core/java/android/os/ParcelDuration.aidl
similarity index 76%
copy from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
copy to core/java/android/os/ParcelDuration.aidl
index 9d05843..e1aa109 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/core/java/android/os/ParcelDuration.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings
+package android.os;
 
-import android.content.ContentResolver
-
-interface CurrentUserContentResolverProvider {
-
-    val currentUserContentResolver: ContentResolver
-}
\ No newline at end of file
+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/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/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/UserManager.java b/core/java/android/os/UserManager.java
index 2465b0e..81ffefd 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -43,7 +43,6 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -1294,7 +1293,7 @@
      * in {@link UserManager} & {@link DevicePolicyManager}.
      * Note: This is slightly different from the real set of user restrictions listed in {@link
      * com.android.server.pm.UserRestrictionsUtils#USER_RESTRICTIONS}. For example
-     * {@link #KEY_RESTRICTIONS_PENDING} is not a real user restriction, but is a a legitimate
+     * {@link #KEY_RESTRICTIONS_PENDING} is not a real user restriction, but is a legitimate
      * value that can be passed into {@link #hasUserRestriction(String)}.
      * @hide
      */
@@ -3174,28 +3173,55 @@
     }
 
     /**
-     * Returns information for all users on this device, including ones marked for deletion.
-     * To retrieve only users that are alive, use {@link #getUsers(boolean)}.
+     * Returns information for all fully-created users on this device, including ones marked for
+     * deletion.
      *
-     * @return the list of users that exist on the device.
+     * <p>To retrieve only users that are not marked for deletion, use {@link #getAliveUsers()}.
+     *
+     * <p>To retrieve *all* users (including partial and pre-created users), use
+     * {@link #getUsers(boolean, boolean, boolean)) getUsers(false, false, false)}.
+     *
+     * <p>To retrieve a more specific list of users, use
+     * {@link #getUsers(boolean, boolean, boolean)}.
+     *
+     * @return the list of users that were created.
+     *
      * @hide
      */
     @UnsupportedAppUsage
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public List<UserInfo> getUsers() {
-        return getUsers(/* excludeDying= */ false);
+        return getUsers(/*excludePartial= */ true, /* excludeDying= */ false,
+                /* excludePreCreated= */ true);
     }
 
     /**
-     * Returns information for all users on this device. Requires
-     * {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * Returns information for all "usable" users on this device (i.e, it excludes users that are
+     * marked for deletion, pre-created users, etc...).
      *
-     * @param excludeDying specify if the list should exclude users being
-     *            removed.
+     * <p>To retrieve all fully-created users, use {@link #getUsers()}.
+     *
+     * <p>To retrieve a more specific list of users, use
+     * {@link #getUsers(boolean, boolean, boolean)}.
+     *
      * @return the list of users that were created.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public @NonNull List<UserInfo> getAliveUsers() {
+        return getUsers(/*excludePartial= */ true, /* excludeDying= */ true,
+                /* excludePreCreated= */ true);
+    }
+
+    /**
+     * @deprecated use {@link #getAliveUsers()} for {@code getUsers(true)}, or
+     * {@link #getUsers()} for @code getUsers(false)}.
+     *
+     * @hide
+     */
+    @Deprecated
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying,
                 /* excludePreCreated= */ true);
@@ -3226,7 +3252,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) {
-        List<UserInfo> users = getUsers(excludeDying);
+        List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+                /* excludePreCreated= */ true);
         List<UserHandle> result = new ArrayList<>(users.size());
         for (UserInfo user : users) {
             result.add(user.getUserHandle());
@@ -3244,7 +3271,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public long[] getSerialNumbersOfUsers(boolean excludeDying) {
-        List<UserInfo> users = getUsers(excludeDying);
+        List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+                /* excludePreCreated= */ true);
         long[] result = new long[users.size()];
         for (int i = 0; i < result.length; i++) {
             result[i] = users.get(i).serialNumber;
@@ -3310,7 +3338,7 @@
     public boolean canAddMoreUsers() {
         // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
         //                    not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
-        final List<UserInfo> users = getUsers(true);
+        final List<UserInfo> users = getAliveUsers();
         final int totalUserCount = users.size();
         int aliveUserCount = 0;
         for (int i = 0; i < totalUserCount; i++) {
@@ -4118,7 +4146,7 @@
 
     /** Returns whether there are any users (other than the current user) to which to switch. */
     private boolean areThereUsersToWhichToSwitch() {
-        final List<UserInfo> users = getUsers(true);
+        final List<UserInfo> users = getAliveUsers();
         if (users == null) {
             return false;
         }
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 61e6a05..be8b929 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -90,9 +90,10 @@
     int unlink(int storageId, in @utf8InCpp String path);
 
     /**
-     * Checks if a file's certain range is loaded. File is specified by its path.
+     * Returns overall loading progress of all the files on a storage, progress value between [0,1].
+     * Returns a negative value on error.
      */
-    boolean isFileRangeLoaded(int storageId, in @utf8InCpp String path, long start, long end);
+    float getLoadingProgress(int storageId);
 
     /**
      * Reads the metadata of a file. File is specified by either its path or 16 byte id.
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index ca6114f..b8dbfbb 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -304,29 +304,21 @@
     }
 
     /**
-     * Checks whether a file under the current storage directory is fully loaded.
+     * Returns the loading progress of a storage
      *
-     * @param path The relative path of the file.
-     * @return True if the file is fully loaded.
+     * @return progress value between [0, 1].
      */
-    public boolean isFileFullyLoaded(@NonNull String path) {
-        return isFileRangeLoaded(path, 0, -1);
-    }
-
-    /**
-     * Checks whether a range in a file if loaded.
-     *
-     * @param path The relative path of the file.
-     * @param start            The starting offset of the range.
-     * @param end              The ending offset of the range.
-     * @return True if the file is fully loaded.
-     */
-    public boolean isFileRangeLoaded(@NonNull String path, long start, long end) {
+    public float getLoadingProgress() throws IOException {
         try {
-            return mService.isFileRangeLoaded(mId, path, start, end);
+            final float res = mService.getLoadingProgress(mId);
+            if (res < 0) {
+                throw new IOException(
+                        "getLoadingProgress() failed at querying loading progress, errno " + -res);
+            }
+            return res;
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
-            return false;
+            return 0;
         }
     }
 
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index dfdb57c4..fb7b232 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -36,8 +36,6 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
 import android.widget.TextView;
 
 /**
@@ -337,9 +335,6 @@
         if (state != null) {
             dialog.onRestoreInstanceState(state);
         }
-        if (needInputMethod()) {
-            requestInputMethod(dialog);
-        }
         dialog.setOnShowListener(new DialogInterface.OnShowListener() {
             @Override
             public void onShow(DialogInterface dialog) {
@@ -380,24 +375,6 @@
     }
 
     /**
-     * Returns whether the preference needs to display a soft input method when the dialog
-     * is displayed. Default is false. Subclasses should override this method if they need
-     * the soft input method brought up automatically.
-     * @hide
-     */
-    protected boolean needInputMethod() {
-        return false;
-    }
-
-    /**
-     * Sets the required flags on the dialog window to enable input method window to show up.
-     */
-    private void requestInputMethod(Dialog dialog) {
-        Window window = dialog.getWindow();
-        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-    }
-
-    /**
      * Creates the content view for the dialog (if a custom content view is
      * required). By default, it inflates the dialog layout resource if it is
      * set.
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index af6f184..75c5102 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.TypedArray;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -28,6 +29,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.WindowInsets;
 import android.widget.EditText;
 
 /**
@@ -123,7 +125,7 @@
 
         EditText editText = mEditText;
         editText.setText(getText());
-        
+
         ViewParent oldParent = editText.getParent();
         if (oldParent != view) {
             if (oldParent != null) {
@@ -133,6 +135,13 @@
         }
     }
 
+    @Override
+    protected void showDialog(Bundle state) {
+        super.showDialog(state);
+        mEditText.requestFocus();
+        mEditText.getWindowInsetsController().show(WindowInsets.Type.ime());
+    }
+
     /**
      * Adds the EditText widget of this preference to the dialog's view.
      * 
@@ -183,13 +192,6 @@
         return mEditText;
     }
 
-    /** @hide */
-    @Override
-    protected boolean needInputMethod() {
-        // We want the input method to show, if possible, when dialog is displayed
-        return true;
-    }
-
     @Override
     protected Parcelable onSaveInstanceState() {
         final Parcelable superState = super.onSaveInstanceState();
diff --git a/core/java/android/preference/OWNERS b/core/java/android/preference/OWNERS
index d20511f..827134e 100644
--- a/core/java/android/preference/OWNERS
+++ b/core/java/android/preference/OWNERS
@@ -1,2 +1,3 @@
+lpf@google.com
 pavlis@google.com
 clarabayarri@google.com
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 276f162..c3b6d8e 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -870,7 +870,7 @@
 
                 // Otherwise, insert to all other users that are running and unlocked.
 
-                final List<UserInfo> users = userManager.getUsers(true);
+                final List<UserInfo> users = userManager.getAliveUsers();
 
                 final int count = users.size();
                 for (int i = 0; i < count; i++) {
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/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..d55fc51 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -218,8 +218,15 @@
     }
 
     /** {@hide} */
-    private void enforceTree(Uri documentUri) {
-        if (isTreeUri(documentUri)) {
+    private void enforceTreeForExtraUris(Bundle extras) {
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI));
+    }
+
+    /** {@hide} */
+    private void enforceTree(@Nullable Uri documentUri) {
+        if (documentUri != null && isTreeUri(documentUri)) {
             final String parent = getTreeDocumentId(documentUri);
             final String child = getDocumentId(documentUri);
             if (Objects.equals(parent, child)) {
@@ -1076,6 +1083,9 @@
         final Context context = getContext();
         final Bundle out = new Bundle();
 
+        // If the URI is a tree URI performs some validation.
+        enforceTreeForExtraUris(extras);
+
         if (METHOD_EJECT_ROOT.equals(method)) {
             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
@@ -1099,9 +1109,6 @@
                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
         }
 
-        // If the URI is a tree URI performs some validation.
-        enforceTree(documentUri);
-
         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
             enforceReadPermissionInner(documentUri, getCallingPackage(),
                     getCallingAttributionTag(), null);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7ad0e7d..03cf0cf 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;
@@ -6403,6 +6404,17 @@
         public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
 
         /**
+         * The current location time zone detection enabled state for the user.
+         *
+         * See {@link
+         * android.app.timezonedetector.TimeZoneDetector#getCapabilities} for access. See {@link
+         * android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update.
+         * @hide
+         */
+        public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED =
+                "location_time_zone_detection_enabled";
+
+        /**
          * The accuracy in meters used for coarsening location for clients with only the coarse
          * location permission.
          *
@@ -8316,6 +8328,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.
          *
@@ -9030,6 +9049,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.
          *
@@ -11830,36 +11856,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:
          *
@@ -12355,63 +12351,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
@@ -12523,18 +12527,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";
 
@@ -12543,8 +12552,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";
 
         /**
@@ -12553,7 +12564,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";
 
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/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 3fcb361..3ed7f6c 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -62,17 +62,17 @@
  * to release the native resources used by the TextToSpeech engine.
  *
  * Apps targeting Android 11 that use text-to-speech should declare {@link
- * TextToSpeech.Engine#INTENT_ACTION_TTS_SERVICE} in the <code><queries></code> elements of their
+ * TextToSpeech.Engine#INTENT_ACTION_TTS_SERVICE} in the {@code queries} elements of their
  * manifest:
  *
- * <code>
- * <queries>
+ * <pre>
+ * &lt;queries&gt;
  *   ...
- *  <intent>
- *      <action android:name="android.intent.action.TTS_SERVICE" />
- *  </intent>
- * </queries>
- * </code>
+ *  &lt;intent&gt;
+ *      &lt;action android:name="android.intent.action.TTS_SERVICE" /&gt;
+ *  &lt;/intent&gt;
+ * &lt;/queries&gt;
+ * </pre>
  */
 public class TextToSpeech {
 
@@ -254,18 +254,17 @@
      * </ul>
      *
      * Apps targeting Android 11 that use text-to-speech should declare {@link
-     * #INTENT_ACTION_TTS_SERVICE} in the <code><queries></code> elements of their
+     * TextToSpeech.Engine#INTENT_ACTION_TTS_SERVICE} in the {@code queries} elements of their
      * manifest:
      *
-     * <code>
-     * <queries>
+     * <pre>
+     * &lt;queries&gt;
      *   ...
-     *  <intent>
-     *      <action android:name="android.intent.action.TTS_SERVICE" />
-     *  </intent>
-     * </queries>
-     * </code>
-
+     *  &lt;intent&gt;
+     *      &lt;action android:name="android.intent.action.TTS_SERVICE" /&gt;
+     *  &lt;/intent&gt;
+     * &lt;/queries&gt;
+     * </pre>
      */
     public class Engine {
 
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/text/OWNERS b/core/java/android/text/OWNERS
index e561371..0b51b2d 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -1,5 +1,4 @@
 set noparent
 
 siyamed@google.com
-nona@google.com
-clarabayarri@google.com
+nona@google.com
\ No newline at end of file
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 6e34666..f74990a 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -149,7 +149,7 @@
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
      * @throws IOException if an I/O error occurs while reading the APK file.
      */
-    private static SignatureInfo findSignature(RandomAccessFile apk)
+    public static SignatureInfo findSignature(RandomAccessFile apk)
             throws IOException, SignatureNotFoundException {
         return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
     }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 9357285..5f963b0 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -142,7 +142,7 @@
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
      * @throws IOException if an I/O error occurs while reading the APK file.
      */
-    private static SignatureInfo findSignature(RandomAccessFile apk)
+    public static SignatureInfo findSignature(RandomAccessFile apk)
             throws IOException, SignatureNotFoundException {
         return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
     }
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index e0258f7..02edb7e 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -92,6 +92,20 @@
     private static PackageParser.SigningDetails verifySignatures(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
             throws PackageParserException {
+        return verifySignaturesInternal(apkPath, minSignatureSchemeVersion,
+                verifyFull).signingDetails;
+    }
+
+    /**
+     * Verifies the provided APK using all allowed signing schemas.
+     * @return the certificates associated with each signer and content digests.
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @throws PackageParserException if there was a problem collecting certificates
+     * @hide
+     */
+    public static SigningDetailsWithDigests verifySignaturesInternal(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+            throws PackageParserException {
 
         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
             // V3 and before are older than the requested minimum signing version
@@ -121,7 +135,7 @@
         return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
     }
 
-    private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
+    private static SigningDetailsWithDigests verifyV3AndBelowSignatures(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
             throws PackageParserException {
         // try v3
@@ -174,7 +188,7 @@
      * @throws SignatureNotFoundException if there are no V4 signatures in the APK
      * @throws PackageParserException     if there was a problem collecting certificates
      */
-    private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
+    private static SigningDetailsWithDigests verifyV4Signature(String apkPath,
             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
             throws SignatureNotFoundException, PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
@@ -234,8 +248,8 @@
                 }
             }
 
-            return new PackageParser.SigningDetails(signerSigs,
-                    SignatureSchemeVersion.SIGNING_BLOCK_V4);
+            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V4), vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
             throw e;
         } catch (Exception e) {
@@ -256,8 +270,8 @@
      * @throws SignatureNotFoundException if there are no V3 signatures in the APK
      * @throws PackageParserException     if there was a problem collecting certificates
      */
-    private static PackageParser.SigningDetails verifyV3Signature(String apkPath,
-            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+    private static SigningDetailsWithDigests verifyV3Signature(String apkPath, boolean verifyFull)
+            throws SignatureNotFoundException, PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
         try {
             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
@@ -275,8 +289,9 @@
                     pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
                 }
             }
-            return new PackageParser.SigningDetails(signerSigs,
-                    SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs);
+            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs),
+                    vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
             throw e;
         } catch (Exception e) {
@@ -297,15 +312,16 @@
      * @throws SignatureNotFoundException if there are no V2 signatures in the APK
      * @throws PackageParserException     if there was a problem collecting certificates
      */
-    private static PackageParser.SigningDetails verifyV2Signature(String apkPath,
-            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+    private static SigningDetailsWithDigests verifyV2Signature(String apkPath, boolean verifyFull)
+            throws SignatureNotFoundException, PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
         try {
-            Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath)
-                    : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
+            ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull);
+            Certificate[][] signerCerts = vSigner.certs;
             Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new PackageParser.SigningDetails(signerSigs,
-                    SignatureSchemeVersion.SIGNING_BLOCK_V2);
+            return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests);
         } catch (SignatureNotFoundException e) {
             throw e;
         } catch (Exception e) {
@@ -324,8 +340,7 @@
      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
      * @throws PackageParserException if there was a problem collecting certificates
      */
-    private static PackageParser.SigningDetails verifyV1Signature(
-            String apkPath, boolean verifyFull)
+    private static SigningDetailsWithDigests verifyV1Signature(String apkPath, boolean verifyFull)
             throws PackageParserException {
         StrictJarFile jarFile = null;
 
@@ -391,7 +406,8 @@
                     }
                 }
             }
-            return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
+            return new SigningDetailsWithDigests(
+                    new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null);
         } catch (GeneralSecurityException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                     "Failed to collect certificates from " + apkPath, e);
@@ -542,4 +558,27 @@
             return null;
         }
     }
+
+    /**
+     * Extended signing details.
+     * @hide for internal use only.
+     */
+    public static class SigningDetailsWithDigests {
+        public final PackageParser.SigningDetails signingDetails;
+
+        /**
+         * APK Signature Schemes v2/v3/v4 might contain multiple content digests.
+         * SignatureVerifier usually chooses one of them to verify.
+         * For certain signature schemes, e.g. v4, this digest is verified continuously.
+         * For others, e.g. v2, the caller has to specify if they want to verify.
+         * Please refer to documentation for more details.
+         */
+        public final Map<Integer, byte[]> contentDigests;
+
+        SigningDetailsWithDigests(PackageParser.SigningDetails signingDetails,
+                Map<Integer, byte[]> contentDigests) {
+            this.signingDetails = signingDetails;
+            this.contentDigests = contentDigests;
+        }
+    }
 }
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
index 990092c..021f232 100644
--- a/core/java/android/util/apk/ApkSigningBlockUtils.java
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -39,7 +39,7 @@
  *
  * @hide for internal use only.
  */
-final class ApkSigningBlockUtils {
+public final class ApkSigningBlockUtils {
 
     private ApkSigningBlockUtils() {
     }
@@ -146,6 +146,37 @@
             Map<Integer, byte[]> expectedDigests,
             FileDescriptor apkFileDescriptor,
             SignatureInfo signatureInfo) throws SecurityException {
+        int[] digestAlgorithms = new int[expectedDigests.size()];
+        int digestAlgorithmCount = 0;
+        for (int digestAlgorithm : expectedDigests.keySet()) {
+            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+            digestAlgorithmCount++;
+        }
+        byte[][] actualDigests;
+        try {
+            actualDigests = computeContentDigestsPer1MbChunk(digestAlgorithms, apkFileDescriptor,
+                    signatureInfo);
+        } catch (DigestException e) {
+            throw new SecurityException("Failed to compute digest(s) of contents", e);
+        }
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+            byte[] actualDigest = actualDigests[i];
+            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+                throw new SecurityException(
+                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                                + " digest of contents did not verify");
+            }
+        }
+    }
+
+    /**
+     * Calculate digests using digestAlgorithms for apkFileDescriptor.
+     * This will skip signature block described by signatureInfo.
+     */
+    public static byte[][] computeContentDigestsPer1MbChunk(int[] digestAlgorithms,
+            FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo) throws DigestException {
         // We need to verify the integrity of the following three sections of the file:
         // 1. Everything up to the start of the APK Signing Block.
         // 2. ZIP Central Directory.
@@ -156,6 +187,7 @@
         // avoid wasting physical memory. In most APK verification scenarios, the contents of the
         // APK are already there in the OS's page cache and thus mmap does not use additional
         // physical memory.
+
         DataSource beforeApkSigningBlock =
                 new MemoryMappedFileDataSource(apkFileDescriptor, 0,
                         signatureInfo.apkSigningBlockOffset);
@@ -171,31 +203,8 @@
         ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
         DataSource eocd = new ByteBufferDataSource(eocdBuf);
 
-        int[] digestAlgorithms = new int[expectedDigests.size()];
-        int digestAlgorithmCount = 0;
-        for (int digestAlgorithm : expectedDigests.keySet()) {
-            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
-            digestAlgorithmCount++;
-        }
-        byte[][] actualDigests;
-        try {
-            actualDigests =
-                    computeContentDigestsPer1MbChunk(
-                            digestAlgorithms,
-                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
-        } catch (DigestException e) {
-            throw new SecurityException("Failed to compute digest(s) of contents", e);
-        }
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
-            byte[] actualDigest = actualDigests[i];
-            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
-                throw new SecurityException(
-                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
-                                + " digest of contents did not verify");
-            }
-        }
+        return computeContentDigestsPer1MbChunk(digestAlgorithms,
+                new DataSource[]{beforeApkSigningBlock, centralDir, eocd});
     }
 
     private static byte[][] computeContentDigestsPer1MbChunk(
@@ -417,14 +426,10 @@
     static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423;
     static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425;
 
-    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
-    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
-    static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
-    static final int CONTENT_DIGEST_SHA256 = 4;
-
-    private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
-            {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
-                    CONTENT_DIGEST_CHUNKED_SHA256};
+    public static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+    public static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+    public static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
+    public static final int CONTENT_DIGEST_SHA256 = 4;
 
     static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
         int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
diff --git a/core/java/android/util/apk/SignatureInfo.java b/core/java/android/util/apk/SignatureInfo.java
index 8e1233a..7638293 100644
--- a/core/java/android/util/apk/SignatureInfo.java
+++ b/core/java/android/util/apk/SignatureInfo.java
@@ -16,15 +16,18 @@
 
 package android.util.apk;
 
+import android.annotation.NonNull;
+
 import java.nio.ByteBuffer;
 
 /**
  * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
  * contained in the block against the file.
+ * @hide
  */
-class SignatureInfo {
+public class SignatureInfo {
     /** Contents of APK Signature Scheme v2 block. */
-    public final ByteBuffer signatureBlock;
+    public final @NonNull ByteBuffer signatureBlock;
 
     /** Position of the APK Signing Block in the file. */
     public final long apkSigningBlockOffset;
@@ -36,10 +39,10 @@
     public final long eocdOffset;
 
     /** Contents of ZIP End of Central Directory (EoCD) of the file. */
-    public final ByteBuffer eocd;
+    public final @NonNull ByteBuffer eocd;
 
-    SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
-            long eocdOffset, ByteBuffer eocd) {
+    SignatureInfo(@NonNull ByteBuffer signatureBlock, long apkSigningBlockOffset,
+            long centralDirOffset, long eocdOffset, @NonNull ByteBuffer eocd) {
         this.signatureBlock = signatureBlock;
         this.apkSigningBlockOffset = apkSigningBlockOffset;
         this.centralDirOffset = centralDirOffset;
diff --git a/core/java/android/util/apk/VerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java
index e81e3f7..4596c6e 100644
--- a/core/java/android/util/apk/VerityBuilder.java
+++ b/core/java/android/util/apk/VerityBuilder.java
@@ -116,6 +116,34 @@
     }
 
     /**
+     * Generates the fs-verity hash tree. It is the actual verity tree format on disk, as is
+     * re-generated on device.
+     *
+     * The tree is built bottom up. The bottom level has 256-bit digest for each 4 KB block in the
+     * input file.  If the total size is larger than 4 KB, take this level as input and repeat the
+     * same procedure, until the level is within 4 KB.  If salt is given, it will apply to each
+     * digestion before the actual data.
+     *
+     * The returned root hash is calculated from the last level of 4 KB chunk, similarly with salt.
+     *
+     * @return the root hash of the generated hash tree.
+     */
+    public static byte[] generateFsVerityRootHash(@NonNull String apkPath, byte[] salt,
+            @NonNull ByteBufferFactory bufferFactory)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            int[] levelOffset = calculateVerityLevelOffset(apk.length());
+            int merkleTreeSize = levelOffset[levelOffset.length - 1];
+
+            ByteBuffer output = bufferFactory.create(
+                    merkleTreeSize
+                            + CHUNK_SIZE_BYTES);  // maximum size of apk-verity metadata
+            output.order(ByteOrder.LITTLE_ENDIAN);
+            ByteBuffer tree = slice(output, 0, merkleTreeSize);
+            return generateFsVerityTreeInternal(apk, salt, levelOffset, tree);
+        }
+    }
+    /**
      * Calculates the apk-verity root hash for integrity measurement.  This needs to be consistent
      * to what kernel returns.
      */
@@ -259,9 +287,10 @@
     // thus the syscall overhead is not too big.
     private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
 
-    private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file, ByteBuffer output)
+    private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file,
+            @Nullable byte[] salt, ByteBuffer output)
             throws IOException, NoSuchAlgorithmException, DigestException {
-        BufferedDigester digester = new BufferedDigester(null /* salt */, output);
+        BufferedDigester digester = new BufferedDigester(salt, output);
 
         // 1. Digest the whole file by chunks.
         consumeByChunk(digester,
@@ -325,6 +354,35 @@
     }
 
     @NonNull
+    private static byte[] generateFsVerityTreeInternal(@NonNull RandomAccessFile apk,
+            @Nullable byte[] salt, @NonNull int[] levelOffset, @NonNull ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        // 1. Digest the apk to generate the leaf level hashes.
+        generateFsVerityDigestAtLeafLevel(apk, salt,
+                slice(output, levelOffset[levelOffset.length - 2],
+                        levelOffset[levelOffset.length - 1]));
+
+        // 2. Digest the lower level hashes bottom up.
+        for (int level = levelOffset.length - 3; level >= 0; level--) {
+            ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
+            ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
+
+            DataSource source = new ByteBufferDataSource(inputBuffer);
+            BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
+            consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
+            digester.assertEmptyBuffer();
+            digester.fillUpLastOutputChunk();
+        }
+
+        // 3. Digest the first block (i.e. first level) to generate the root hash.
+        byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
+        BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
+        digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
+        digester.assertEmptyBuffer();
+        return rootHash;
+    }
+
+    @NonNull
     private static byte[] generateVerityTreeInternal(@NonNull RandomAccessFile apk,
             @Nullable SignatureInfo signatureInfo, @Nullable byte[] salt,
             @NonNull int[] levelOffset, @NonNull ByteBuffer output)
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 25a4108..a06a0c6 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -25,6 +25,7 @@
 
 import dalvik.system.CloseGuard;
 
+import java.io.PrintWriter;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 
@@ -53,6 +54,7 @@
     private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
     private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
             long frameTimeNanos);
+    private static native String nativeDump(long receiverPtr, String prefix);
 
     /**
      * Creates an input event receiver bound to the specified input channel.
@@ -221,6 +223,18 @@
     }
 
     /**
+     * Dump the state of this InputEventReceiver to the writer.
+     * @param prefix the prefix (typically whitespace padding) to append in front of each line
+     * @param writer the writer where the dump should be written
+     */
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + getClass().getName());
+        writer.println(prefix + " mInputChannel: " + mInputChannel);
+        writer.println(prefix + " mSeqMap: " + mSeqMap);
+        writer.println(prefix + " mReceiverPtr:\n" + nativeDump(mReceiverPtr, prefix + "  "));
+    }
+
+    /**
      * Factory for InputEventReceiver
      */
     public interface Factory {
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index e341845..1ef701f 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -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 7f45c04..403ac3a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -629,7 +629,7 @@
             if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
             mHost.notifyInsetsChanged();
         }
-        if (!mState.equals(state, true /* excludingCaptionInsets */,
+        if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */,
                 true /* excludeInvisibleIme */)) {
             if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState);
             updateRequestedState();
@@ -1138,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());
@@ -1154,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/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 6136a80..0c3d61f 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -456,8 +456,8 @@
                 case MotionEvent.ACTION_UP:
                     if (mTrackGesture) {
                         if (mFeedbackIcon.isVisibleToUser()
-                                && (mFeedbackRect.contains((int) x, (int) y))
-                                || mFeedbackRect.contains((int) mDownX, (int) mDownY)) {
+                                && (mFeedbackRect.contains((int) x, (int) y)
+                                || mFeedbackRect.contains((int) mDownX, (int) mDownY))) {
                             mFeedbackIcon.performClick();
                             return true;
                         }
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 3f02d70..6e17ac9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7532,36 +7532,36 @@
 
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         String innerPrefix = prefix + "  ";
-        writer.print(prefix); writer.println("ViewRoot:");
-        writer.print(innerPrefix); writer.print("mAdded="); writer.print(mAdded);
-                writer.print(" mRemoved="); writer.println(mRemoved);
-        writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
-                writer.println(mConsumeBatchedInputScheduled);
-        writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
-                writer.println(mConsumeBatchedInputImmediatelyScheduled);
-        writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
-                writer.println(mPendingInputEventCount);
-        writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
-                writer.println(mProcessInputEventsScheduled);
-        writer.print(innerPrefix); writer.print("mTraversalScheduled=");
-                writer.print(mTraversalScheduled);
-        writer.print(innerPrefix); writer.print("mIsAmbientMode=");
-                writer.print(mIsAmbientMode);
-        writer.print(innerPrefix); writer.print("mUnbufferedInputSource=");
-        writer.print(Integer.toHexString(mUnbufferedInputSource));
-
+        writer.println(prefix + "ViewRoot:");
+        writer.println(innerPrefix + "mAdded=" + mAdded);
+        writer.println(innerPrefix + "mRemoved=" + mRemoved);
+        writer.println(innerPrefix + "mStopped=" + mStopped);
+        writer.println(innerPrefix + "mConsumeBatchedInputScheduled="
+                + mConsumeBatchedInputScheduled);
+        writer.println(innerPrefix + "mConsumeBatchedInputImmediatelyScheduled="
+                + mConsumeBatchedInputImmediatelyScheduled);
+        writer.println(innerPrefix + "mPendingInputEventCount=" + mPendingInputEventCount);
+        writer.println(innerPrefix + "mProcessInputEventsScheduled="
+                + mProcessInputEventsScheduled);
+        writer.println(innerPrefix + "mTraversalScheduled=" + mTraversalScheduled);
         if (mTraversalScheduled) {
-            writer.print(" (barrier="); writer.print(mTraversalBarrier); writer.println(")");
-        } else {
-            writer.println();
+            writer.println(innerPrefix + " (barrier=" + mTraversalBarrier + ")");
         }
+        writer.println(innerPrefix + "mIsAmbientMode="  + mIsAmbientMode);
+        writer.println(innerPrefix + "mUnbufferedInputSource="
+                + Integer.toHexString(mUnbufferedInputSource));
+
         mFirstInputStage.dump(innerPrefix, writer);
 
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dump(innerPrefix, writer);
+        }
+
         mChoreographer.dump(prefix, writer);
 
         mInsetsController.dump(prefix, writer);
 
-        writer.print(prefix); writer.println("View Hierarchy:");
+        writer.println(prefix + "View Hierarchy:");
         dumpViewHierarchy(innerPrefix, writer, mView);
     }
 
@@ -8890,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/WindowManager.java b/core/java/android/view/WindowManager.java
index 7923ff0..32ee290 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -534,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;
@@ -542,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/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/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
index 7e7b987..b04105a 100644
--- a/core/java/android/webkit/PacProcessor.java
+++ b/core/java/android/webkit/PacProcessor.java
@@ -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
@@ -43,14 +47,21 @@
      * Returns PacProcessor instance associated with the {@link Network}.
      * The host resolution is done on this {@link Network}.
      *
-     * @param networkHandle a handle representing {@link Network} handle.
-     * @return PacProcessor instance for the specified network.
-     * @see Network#getNetworkHandle
-     * @see Network#fromNetworkHandle
+     * <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(long networkHandle) {
-        return WebViewFactory.getProvider().getPacProcessorForNetwork(networkHandle);
+    static PacProcessor getInstanceForNetwork(@Nullable Network network) {
+        return WebViewFactory.getProvider().getPacProcessorForNetwork(network);
     }
 
     /**
@@ -73,19 +84,22 @@
     /**
      * 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 network handle associated with this {@link PacProcessor}.
+     * Returns a {@link Network} associated with this {@link PacProcessor}.
      *
-     * @return a network handle or 0 if a network is unspecified.
-     * @see Network#getNetworkHandle
-     * @see Network#fromNetworkHandle
+     * @return an associated {@link Network} or {@code null} if a network is unspecified.
      */
-    default long getNetworkHandle() {
+    @Nullable
+    default Network getNetwork() {
         throw new UnsupportedOperationException("Not implemented");
     }
 }
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index f1863e3..ce999cd 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
@@ -188,13 +189,13 @@
      * Returns PacProcessor instance associated with the {@link Network}.
      * The host resolution is done on this {@link Network}.
      *
-     * @param networkHandle a network handle representing the {@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}.
-     * @see Network#getNetworkHandle
-     * @see Network#fromNetworkHandle
      */
     @NonNull
-    default PacProcessor getPacProcessorForNetwork(long networkHandle) {
+    default PacProcessor getPacProcessorForNetwork(@Nullable Network network) {
         throw new UnsupportedOperationException("Not implemented");
     }
 
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/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index eb59f0f..da26930 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -409,6 +409,11 @@
      */
     public static final String BACK_GESTURE_SLOP_MULTIPLIER = "back_gesture_slop_multiplier";
 
+    /**
+     * (long) Screenshot keychord delay (how long the buttons must be pressed), in ms
+     */
+    public static final String SCREENSHOT_KEYCHORD_DELAY = "screenshot_keychord_delay";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a50a522..3b5fecf 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -113,6 +113,14 @@
         // Default is no-op
     }
 
+    /**
+     * Callback indicating that the given document has been deleted or moved. This gives
+     * the provider a hook to revoke the uri permissions.
+     */
+    protected void onDocIdDeleted(String docId) {
+        // Default is no-op
+    }
+
     @Override
     public boolean onCreate() {
         throw new UnsupportedOperationException(
@@ -283,6 +291,7 @@
 
         final String afterDocId = getDocIdForFile(after);
         onDocIdChanged(docId);
+        onDocIdDeleted(docId);
         onDocIdChanged(afterDocId);
 
         final File afterVisibleFile = getFileForDocId(afterDocId, true);
@@ -312,6 +321,7 @@
 
         final String docId = getDocIdForFile(after);
         onDocIdChanged(sourceDocumentId);
+        onDocIdDeleted(sourceDocumentId);
         onDocIdChanged(docId);
         moveInMediaStore(visibleFileBefore, getFileForDocId(docId, true));
 
@@ -343,6 +353,7 @@
         }
 
         onDocIdChanged(docId);
+        onDocIdDeleted(docId);
         removeFromMediaStore(visibleFile);
     }
 
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/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 053b06f..2f8c457 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -347,7 +347,7 @@
         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;
     }
 
     /**
diff --git a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
new file mode 100644
index 0000000..8a4eb4a
--- /dev/null
+++ b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.protolog;
+
+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;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogDataType;
+import com.android.internal.util.TraceBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * A service for the ProtoLog logging system.
+ */
+public class BaseProtoLogImpl {
+    protected static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
+
+    /**
+     * A runnable to update the cached output of {@link #isEnabled}.
+     *
+     * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
+     * starting / stopping proto log, or enabling / disabling log groups.
+     */
+    public static Runnable sCacheUpdater = () -> { };
+
+    protected static void addLogGroupEnum(IProtoLogGroup[] config) {
+        for (IProtoLogGroup group : config) {
+            LOG_GROUPS.put(group.name(), group);
+        }
+    }
+
+    private static final String TAG = "ProtoLog";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+    static final String PROTOLOG_VERSION = "1.0.0";
+
+    private final File mLogFile;
+    private final String mViewerConfigFilename;
+    private final TraceBuffer mBuffer;
+    protected final ProtoLogViewerConfigReader mViewerConfig;
+
+    private boolean mProtoLogEnabled;
+    private boolean mProtoLogEnabledLockFree;
+    private final Object mProtoLogEnabledLock = new Object();
+
+    @VisibleForTesting
+    public enum LogLevel {
+        DEBUG, VERBOSE, INFO, WARN, ERROR, WTF
+    }
+
+    /**
+     * Main log method, do not call directly.
+     */
+    @VisibleForTesting
+    public void log(LogLevel level, IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString, Object[] args) {
+        if (group.isLogToProto()) {
+            logToProto(messageHash, paramsMask, args);
+        }
+        if (group.isLogToLogcat()) {
+            logToLogcat(group.getTag(), level, messageHash, messageString, args);
+        }
+    }
+
+    private void logToLogcat(String tag, LogLevel level, int messageHash,
+            @Nullable String messageString, Object[] args) {
+        String message = null;
+        if (messageString == null) {
+            messageString = mViewerConfig.getViewerString(messageHash);
+        }
+        if (messageString != null) {
+            try {
+                message = String.format(messageString, args);
+            } catch (IllegalFormatConversionException ex) {
+                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+            }
+        }
+        if (message == null) {
+            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+            for (Object o : args) {
+                builder.append(" ").append(o);
+            }
+            message = builder.toString();
+        }
+        passToLogcat(tag, level, message);
+    }
+
+    /**
+     * SLog wrapper.
+     */
+    @VisibleForTesting
+    public void passToLogcat(String tag, LogLevel level, String message) {
+        switch (level) {
+            case DEBUG:
+                Slog.d(tag, message);
+                break;
+            case VERBOSE:
+                Slog.v(tag, message);
+                break;
+            case INFO:
+                Slog.i(tag, message);
+                break;
+            case WARN:
+                Slog.w(tag, message);
+                break;
+            case ERROR:
+                Slog.e(tag, message);
+                break;
+            case WTF:
+                Slog.wtf(tag, message);
+                break;
+        }
+    }
+
+    private void logToProto(int messageHash, int paramsMask, Object[] args) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        try {
+            ProtoOutputStream os = new ProtoOutputStream();
+            long token = os.start(LOG);
+            os.write(MESSAGE_HASH, messageHash);
+            os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+
+            if (args != null) {
+                int argIndex = 0;
+                ArrayList<Long> longParams = new ArrayList<>();
+                ArrayList<Double> doubleParams = new ArrayList<>();
+                ArrayList<Boolean> booleanParams = new ArrayList<>();
+                for (Object o : args) {
+                    int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+                    try {
+                        switch (type) {
+                            case LogDataType.STRING:
+                                os.write(STR_PARAMS, o.toString());
+                                break;
+                            case LogDataType.LONG:
+                                longParams.add(((Number) o).longValue());
+                                break;
+                            case LogDataType.DOUBLE:
+                                doubleParams.add(((Number) o).doubleValue());
+                                break;
+                            case LogDataType.BOOLEAN:
+                                booleanParams.add((boolean) o);
+                                break;
+                        }
+                    } catch (ClassCastException ex) {
+                        // Should not happen unless there is an error in the ProtoLogTool.
+                        os.write(STR_PARAMS, "(INVALID PARAMS_MASK) " + o.toString());
+                        Slog.e(TAG, "Invalid ProtoLog paramsMask", ex);
+                    }
+                    argIndex++;
+                }
+                if (longParams.size() > 0) {
+                    os.writePackedSInt64(SINT64_PARAMS,
+                            longParams.stream().mapToLong(i -> i).toArray());
+                }
+                if (doubleParams.size() > 0) {
+                    os.writePackedDouble(DOUBLE_PARAMS,
+                            doubleParams.stream().mapToDouble(i -> i).toArray());
+                }
+                if (booleanParams.size() > 0) {
+                    boolean[] arr = new boolean[booleanParams.size()];
+                    for (int i = 0; i < booleanParams.size(); i++) {
+                        arr[i] = booleanParams.get(i);
+                    }
+                    os.writePackedBool(BOOLEAN_PARAMS, arr);
+                }
+            }
+            os.end(token);
+            mBuffer.add(os);
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception while logging to proto", e);
+        }
+    }
+
+    public BaseProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
+            ProtoLogViewerConfigReader viewerConfig) {
+        mLogFile = file;
+        mBuffer = new TraceBuffer(bufferCapacity);
+        mViewerConfigFilename = viewerConfigFilename;
+        mViewerConfig = viewerConfig;
+    }
+
+    /**
+     * Starts the logging a circular proto buffer.
+     *
+     * @param pw Print writer
+     */
+    public void startProtoLog(@Nullable PrintWriter pw) {
+        if (isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Start logging to " + mLogFile + ".");
+            mBuffer.resetBuffer();
+            mProtoLogEnabled = true;
+            mProtoLogEnabledLockFree = true;
+        }
+        sCacheUpdater.run();
+    }
+
+    /**
+     * Stops logging to proto.
+     *
+     * @param pw          Print writer
+     * @param writeToFile If the current buffer should be written to disk or not
+     */
+    public void stopProtoLog(@Nullable PrintWriter pw, boolean writeToFile) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Stop logging to " + mLogFile + ". Waiting for log to flush.");
+            mProtoLogEnabled = mProtoLogEnabledLockFree = false;
+            if (writeToFile) {
+                writeProtoLogToFileLocked();
+                logAndPrintln(pw, "Log written to " + mLogFile + ".");
+            }
+            if (mProtoLogEnabled) {
+                logAndPrintln(pw, "ERROR: logging was re-enabled while waiting for flush.");
+                throw new IllegalStateException("logging enabled while waiting for flush.");
+            }
+        }
+        sCacheUpdater.run();
+    }
+
+    /**
+     * Returns {@code true} iff logging to proto is enabled.
+     */
+    public boolean isProtoEnabled() {
+        return mProtoLogEnabledLockFree;
+    }
+
+    protected int setLogging(boolean setTextLogging, boolean value, PrintWriter pw,
+            String... groups) {
+        for (int i = 0; i < groups.length; i++) {
+            String group = groups[i];
+            IProtoLogGroup g = LOG_GROUPS.get(group);
+            if (g != null) {
+                System.out.println("G: "+ g);
+                if (setTextLogging) {
+                    g.setLogToLogcat(value);
+                } else {
+                    g.setLogToProto(value);
+                }
+            } else {
+                logAndPrintln(pw, "No IProtoLogGroup named " + group);
+                return -1;
+            }
+        }
+        sCacheUpdater.run();
+        return 0;
+    }
+
+    private int unknownCommand(PrintWriter pw) {
+        pw.println("Unknown command");
+        pw.println("Window manager logging options:");
+        pw.println("  start: Start proto logging");
+        pw.println("  stop: Stop proto logging");
+        pw.println("  enable [group...]: Enable proto logging for given groups");
+        pw.println("  disable [group...]: Disable proto logging for given groups");
+        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
+        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
+        return -1;
+    }
+
+    /**
+     * Responds to a shell command.
+     */
+    public int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        String cmd = shell.getNextArg();
+        if (cmd == null) {
+            return unknownCommand(pw);
+        }
+        ArrayList<String> args = new ArrayList<>();
+        String arg;
+        while ((arg = shell.getNextArg()) != null) {
+            args.add(arg);
+        }
+        String[] groups = args.toArray(new String[args.size()]);
+        switch (cmd) {
+            case "start":
+                startProtoLog(pw);
+                return 0;
+            case "stop":
+                stopProtoLog(pw, true);
+                return 0;
+            case "status":
+                logAndPrintln(pw, getStatus());
+                return 0;
+            case "enable":
+                return setLogging(false, true, pw, groups);
+            case "enable-text":
+                mViewerConfig.loadViewerConfig(pw, mViewerConfigFilename);
+                return setLogging(true, true, pw, groups);
+            case "disable":
+                return setLogging(false, false, pw, groups);
+            case "disable-text":
+                return setLogging(true, false, pw, groups);
+            default:
+                return unknownCommand(pw);
+        }
+    }
+
+    /**
+     * Returns a human-readable ProtoLog status text.
+     */
+    public String getStatus() {
+        return "ProtoLog status: "
+                + ((isProtoEnabled()) ? "Enabled" : "Disabled")
+                + "\nEnabled log groups: \n  Proto: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToProto())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\n  Logcat: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToLogcat())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber();
+    }
+
+    /**
+     * Writes the log buffer to a new file for the bugreport.
+     *
+     * This method is synchronized with {@code #startProtoLog(PrintWriter)} and
+     * {@link #stopProtoLog(PrintWriter, boolean)}.
+     */
+    public void writeProtoLogToFile() {
+        synchronized (mProtoLogEnabledLock) {
+            writeProtoLogToFileLocked();
+        }
+    }
+
+    private void writeProtoLogToFileLocked() {
+        try {
+            long offset =
+                    (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
+            ProtoOutputStream proto = new ProtoOutputStream();
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            proto.write(VERSION, PROTOLOG_VERSION);
+            proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
+            mBuffer.writeTraceToFile(mLogFile, proto);
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to write buffer to file", e);
+        }
+    }
+
+    static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+        Slog.i(TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
+}
+
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 73d148c..9f7436a 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -36,7 +36,18 @@
             Consts.TAG_WM),
     WM_DEBUG_ADD_REMOVE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
-    WM_DEBUG_FOCUS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_CONFIGURATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_SWITCH(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_CONTAINERS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_FOCUS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_IMMERSIVE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_LOCKTASK(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
     WM_DEBUG_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
     WM_SHOW_TRANSACTIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -59,6 +70,8 @@
             Consts.TAG_WM),
     WM_DEBUG_IME(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
+    WM_DEBUG_WINDOW_ORGANIZER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 6874f10..10224a4 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -16,58 +16,22 @@
 
 package com.android.internal.protolog;
 
-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;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.internal.protolog.common.LogDataType;
-import com.android.internal.util.TraceBuffer;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.IllegalFormatConversionException;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-
 
 /**
  * A service for the ProtoLog logging system.
  */
-public class ProtoLogImpl {
-    private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
+public class ProtoLogImpl extends BaseProtoLogImpl {
+    private static final int BUFFER_CAPACITY = 1024 * 1024;
+    private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
+    private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
 
-    /**
-     * A runnable to update the cached output of {@link #isEnabled}.
-     *
-     * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
-     * starting / stopping proto log, or enabling / disabling log groups.
-     */
-    public static Runnable sCacheUpdater = () -> { };
-
-    private static void addLogGroupEnum(IProtoLogGroup[] config) {
-        for (IProtoLogGroup group : config) {
-            LOG_GROUPS.put(group.name(), group);
-        }
-    }
+    private static ProtoLogImpl sServiceInstance = null;
 
     static {
         addLogGroupEnum(ProtoLogGroup.values());
@@ -124,30 +88,13 @@
                 || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
     }
 
-    private static final int BUFFER_CAPACITY = 1024 * 1024;
-    private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
-    private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
-    private static final String TAG = "ProtoLog";
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-    static final String PROTOLOG_VERSION = "1.0.0";
-
-    private final File mLogFile;
-    private final TraceBuffer mBuffer;
-    private final ProtoLogViewerConfigReader mViewerConfig;
-
-    private boolean mProtoLogEnabled;
-    private boolean mProtoLogEnabledLockFree;
-    private final Object mProtoLogEnabledLock = new Object();
-
-    private static ProtoLogImpl sServiceInstance = null;
-
     /**
      * Returns the single instance of the ProtoLogImpl singleton class.
      */
     public static synchronized ProtoLogImpl getSingleInstance() {
         if (sServiceInstance == null) {
-            sServiceInstance = new ProtoLogImpl(new File(LOG_FILENAME), BUFFER_CAPACITY,
-                    new ProtoLogViewerConfigReader());
+            sServiceInstance = new ProtoLogImpl(
+                    new File(LOG_FILENAME), BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
         }
         return sServiceInstance;
     }
@@ -157,307 +104,9 @@
         sServiceInstance = instance;
     }
 
-    @VisibleForTesting
-    public enum LogLevel {
-        DEBUG, VERBOSE, INFO, WARN, ERROR, WTF
-    }
-
-    /**
-     * Main log method, do not call directly.
-     */
-    @VisibleForTesting
-    public void log(LogLevel level, IProtoLogGroup group, int messageHash, int paramsMask,
-            @Nullable String messageString, Object[] args) {
-        if (group.isLogToProto()) {
-            logToProto(messageHash, paramsMask, args);
-        }
-        if (group.isLogToLogcat()) {
-            logToLogcat(group.getTag(), level, messageHash, messageString, args);
-        }
-    }
-
-    private void logToLogcat(String tag, LogLevel level, int messageHash,
-            @Nullable String messageString, Object[] args) {
-        String message = null;
-        if (messageString == null) {
-            messageString = mViewerConfig.getViewerString(messageHash);
-        }
-        if (messageString != null) {
-            try {
-                message = String.format(messageString, args);
-            } catch (IllegalFormatConversionException ex) {
-                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
-            }
-        }
-        if (message == null) {
-            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
-            for (Object o : args) {
-                builder.append(" ").append(o);
-            }
-            message = builder.toString();
-        }
-        passToLogcat(tag, level, message);
-    }
-
-    /**
-     * SLog wrapper.
-     */
-    @VisibleForTesting
-    public void passToLogcat(String tag, LogLevel level, String message) {
-        switch (level) {
-            case DEBUG:
-                Slog.d(tag, message);
-                break;
-            case VERBOSE:
-                Slog.v(tag, message);
-                break;
-            case INFO:
-                Slog.i(tag, message);
-                break;
-            case WARN:
-                Slog.w(tag, message);
-                break;
-            case ERROR:
-                Slog.e(tag, message);
-                break;
-            case WTF:
-                Slog.wtf(tag, message);
-                break;
-        }
-    }
-
-    private void logToProto(int messageHash, int paramsMask, Object[] args) {
-        if (!isProtoEnabled()) {
-            return;
-        }
-        try {
-            ProtoOutputStream os = new ProtoOutputStream();
-            long token = os.start(LOG);
-            os.write(MESSAGE_HASH, messageHash);
-            os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
-
-            if (args != null) {
-                int argIndex = 0;
-                ArrayList<Long> longParams = new ArrayList<>();
-                ArrayList<Double> doubleParams = new ArrayList<>();
-                ArrayList<Boolean> booleanParams = new ArrayList<>();
-                for (Object o : args) {
-                    int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
-                    try {
-                        switch (type) {
-                            case LogDataType.STRING:
-                                os.write(STR_PARAMS, o.toString());
-                                break;
-                            case LogDataType.LONG:
-                                longParams.add(((Number) o).longValue());
-                                break;
-                            case LogDataType.DOUBLE:
-                                doubleParams.add(((Number) o).doubleValue());
-                                break;
-                            case LogDataType.BOOLEAN:
-                                booleanParams.add((boolean) o);
-                                break;
-                        }
-                    } catch (ClassCastException ex) {
-                        // Should not happen unless there is an error in the ProtoLogTool.
-                        os.write(STR_PARAMS, "(INVALID PARAMS_MASK) " + o.toString());
-                        Slog.e(TAG, "Invalid ProtoLog paramsMask", ex);
-                    }
-                    argIndex++;
-                }
-                if (longParams.size() > 0) {
-                    os.writePackedSInt64(SINT64_PARAMS,
-                            longParams.stream().mapToLong(i -> i).toArray());
-                }
-                if (doubleParams.size() > 0) {
-                    os.writePackedDouble(DOUBLE_PARAMS,
-                            doubleParams.stream().mapToDouble(i -> i).toArray());
-                }
-                if (booleanParams.size() > 0) {
-                    boolean[] arr = new boolean[booleanParams.size()];
-                    for (int i = 0; i < booleanParams.size(); i++) {
-                        arr[i] = booleanParams.get(i);
-                    }
-                    os.writePackedBool(BOOLEAN_PARAMS, arr);
-                }
-            }
-            os.end(token);
-            mBuffer.add(os);
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception while logging to proto", e);
-        }
-    }
-
-    public ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
-        mLogFile = file;
-        mBuffer = new TraceBuffer(bufferCapacity);
-        mViewerConfig = viewerConfig;
-    }
-
-    /**
-     * Starts the logging a circular proto buffer.
-     *
-     * @param pw Print writer
-     */
-    public void startProtoLog(@Nullable PrintWriter pw) {
-        if (isProtoEnabled()) {
-            return;
-        }
-        synchronized (mProtoLogEnabledLock) {
-            logAndPrintln(pw, "Start logging to " + mLogFile + ".");
-            mBuffer.resetBuffer();
-            mProtoLogEnabled = true;
-            mProtoLogEnabledLockFree = true;
-        }
-        sCacheUpdater.run();
-    }
-
-    /**
-     * Stops logging to proto.
-     *
-     * @param pw          Print writer
-     * @param writeToFile If the current buffer should be written to disk or not
-     */
-    public void stopProtoLog(@Nullable PrintWriter pw, boolean writeToFile) {
-        if (!isProtoEnabled()) {
-            return;
-        }
-        synchronized (mProtoLogEnabledLock) {
-            logAndPrintln(pw, "Stop logging to " + mLogFile + ". Waiting for log to flush.");
-            mProtoLogEnabled = mProtoLogEnabledLockFree = false;
-            if (writeToFile) {
-                writeProtoLogToFileLocked();
-                logAndPrintln(pw, "Log written to " + mLogFile + ".");
-            }
-            if (mProtoLogEnabled) {
-                logAndPrintln(pw, "ERROR: logging was re-enabled while waiting for flush.");
-                throw new IllegalStateException("logging enabled while waiting for flush.");
-            }
-        }
-        sCacheUpdater.run();
-    }
-
-    /**
-     * Returns {@code true} iff logging to proto is enabled.
-     */
-    public boolean isProtoEnabled() {
-        return mProtoLogEnabledLockFree;
-    }
-
-    private int setLogging(ShellCommand shell, boolean setTextLogging, boolean value) {
-        String group;
-        while ((group = shell.getNextArg()) != null) {
-            IProtoLogGroup g = LOG_GROUPS.get(group);
-            if (g != null) {
-                if (setTextLogging) {
-                    g.setLogToLogcat(value);
-                } else {
-                    g.setLogToProto(value);
-                }
-            } else {
-                logAndPrintln(shell.getOutPrintWriter(), "No IProtoLogGroup named " + group);
-                return -1;
-            }
-        }
-        sCacheUpdater.run();
-        return 0;
-    }
-
-    private int unknownCommand(PrintWriter pw) {
-        pw.println("Unknown command");
-        pw.println("Window manager logging options:");
-        pw.println("  start: Start proto logging");
-        pw.println("  stop: Stop proto logging");
-        pw.println("  enable [group...]: Enable proto logging for given groups");
-        pw.println("  disable [group...]: Disable proto logging for given groups");
-        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
-        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
-        return -1;
-    }
-
-    /**
-     * Responds to a shell command.
-     */
-    public int onShellCommand(ShellCommand shell) {
-        PrintWriter pw = shell.getOutPrintWriter();
-        String cmd = shell.getNextArg();
-        if (cmd == null) {
-            return unknownCommand(pw);
-        }
-        switch (cmd) {
-            case "start":
-                startProtoLog(pw);
-                return 0;
-            case "stop":
-                stopProtoLog(pw, true);
-                return 0;
-            case "status":
-                logAndPrintln(pw, getStatus());
-                return 0;
-            case "enable":
-                return setLogging(shell, false, true);
-            case "enable-text":
-                mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
-                return setLogging(shell, true, true);
-            case "disable":
-                return setLogging(shell, false, false);
-            case "disable-text":
-                return setLogging(shell, true, false);
-            default:
-                return unknownCommand(pw);
-        }
-    }
-
-    /**
-     * Returns a human-readable ProtoLog status text.
-     */
-    public String getStatus() {
-        return "ProtoLog status: "
-                + ((isProtoEnabled()) ? "Enabled" : "Disabled")
-                + "\nEnabled log groups: \n  Proto: "
-                + LOG_GROUPS.values().stream().filter(
-                    it -> it.isEnabled() && it.isLogToProto())
-                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
-                + "\n  Logcat: "
-                + LOG_GROUPS.values().stream().filter(
-                    it -> it.isEnabled() && it.isLogToLogcat())
-                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
-                + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber();
-    }
-
-    /**
-     * Writes the log buffer to a new file for the bugreport.
-     *
-     * This method is synchronized with {@code #startProtoLog(PrintWriter)} and
-     * {@link #stopProtoLog(PrintWriter, boolean)}.
-     */
-    public void writeProtoLogToFile() {
-        synchronized (mProtoLogEnabledLock) {
-            writeProtoLogToFileLocked();
-        }
-    }
-
-    private void writeProtoLogToFileLocked() {
-        try {
-            long offset =
-                    (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
-            ProtoOutputStream proto = new ProtoOutputStream();
-            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
-            proto.write(VERSION, PROTOLOG_VERSION);
-            proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
-            mBuffer.writeTraceToFile(mLogFile, proto);
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to write buffer to file", e);
-        }
-    }
-
-
-    static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
-        Slog.i(TAG, msg);
-        if (pw != null) {
-            pw.println(msg);
-            pw.flush();
-        }
+    public ProtoLogImpl(File logFile, int bufferCapacity,
+            ProtoLogViewerConfigReader viewConfigReader) {
+        super(logFile, VIEWER_CONFIG_FILENAME, bufferCapacity, viewConfigReader);
     }
 }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index e381d30..aa30a77 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.protolog;
 
+import android.annotation.Nullable;
+import android.util.Slog;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -23,6 +26,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.Iterator;
@@ -34,6 +38,7 @@
  * Handles loading and parsing of ProtoLog viewer configuration.
  */
 public class ProtoLogViewerConfigReader {
+    private static final String TAG = "ProtoLogViewerConfigReader";
     private Map<Integer, String> mLogMessageMap = null;
 
     /** Returns message format string for its hash or null if unavailable. */
@@ -49,48 +54,54 @@
      * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
      */
     public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
+        try {
+            loadViewerConfig(new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
+            logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
+                    + " log definitions from " + viewerConfigFilename);
+        } catch (FileNotFoundException e) {
+            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 "
+                    + viewerConfigFilename + ". " + e);
+        } catch (JSONException e) {
+            logAndPrintln(pw, "Unable to load log definitions: JSON parsing exception while reading "
+                    + viewerConfigFilename + ". " + e);
+        }
+    }
+
+    /**
+     * Reads the specified viewer configuration input stream.
+     * Does nothing if the config is already loaded.
+     */
+    public synchronized void loadViewerConfig(InputStream viewerConfigInputStream)
+            throws IOException, JSONException {
         if (mLogMessageMap != null) {
             return;
         }
-        try {
-            InputStreamReader config = new InputStreamReader(
-                    new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
-            BufferedReader reader = new BufferedReader(config);
-            StringBuilder builder = new StringBuilder();
-            String line;
-            while ((line = reader.readLine()) != null) {
-                builder.append(line).append('\n');
-            }
-            reader.close();
-            JSONObject json = new JSONObject(builder.toString());
-            JSONObject messages = json.getJSONObject("messages");
+        InputStreamReader config = new InputStreamReader(viewerConfigInputStream);
+        BufferedReader reader = new BufferedReader(config);
+        StringBuilder builder = new StringBuilder();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            builder.append(line).append('\n');
+        }
+        reader.close();
+        JSONObject json = new JSONObject(builder.toString());
+        JSONObject messages = json.getJSONObject("messages");
 
-            mLogMessageMap = new TreeMap<>();
-            Iterator it = messages.keys();
-            while (it.hasNext()) {
-                String key = (String) it.next();
-                try {
-                    int hash = Integer.parseInt(key);
-                    JSONObject val = messages.getJSONObject(key);
-                    String msg = val.getString("message");
-                    mLogMessageMap.put(hash, msg);
-                } catch (NumberFormatException expected) {
-                    // Not a messageHash - skip it
-                }
+        mLogMessageMap = new TreeMap<>();
+        Iterator it = messages.keys();
+        while (it.hasNext()) {
+            String key = (String) it.next();
+            try {
+                int hash = Integer.parseInt(key);
+                JSONObject val = messages.getJSONObject(key);
+                String msg = val.getString("message");
+                mLogMessageMap.put(hash, msg);
+            } catch (NumberFormatException expected) {
+                // Not a messageHash - skip it
             }
-            ProtoLogImpl.logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
-                    + " log definitions from " + viewerConfigFilename);
-        } catch (FileNotFoundException e) {
-            ProtoLogImpl.logAndPrintln(pw, "Unable to load log definitions: File "
-                    + viewerConfigFilename + " not found." + e);
-        } catch (IOException e) {
-            ProtoLogImpl.logAndPrintln(pw,
-                    "Unable to load log definitions: IOException while reading "
-                    + viewerConfigFilename + ". " + e);
-        } catch (JSONException e) {
-            ProtoLogImpl.logAndPrintln(pw,
-                    "Unable to load log definitions: JSON parsing exception while reading "
-                            + viewerConfigFilename + ". " + e);
         }
     }
 
@@ -103,4 +114,12 @@
         }
         return 0;
     }
+
+    static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+        Slog.i(TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
 }
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/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index c82a656..937b942 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -200,6 +200,20 @@
     }
 
     /**
+     * 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.
      *
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index d5d635d..654b461 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -49,7 +49,8 @@
             in ICheckCredentialProgressCallback progressCallback);
     VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, int userId, int flags);
     VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, int userId, int flags);
-    VerifyCredentialResponse verifyGatekeeperPassword(in byte[] gatekeeperPassword, long challenge, int userId);
+    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/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index 2302de2..b4e108f 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.util.Log;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -31,6 +32,7 @@
  * A class to extract Bitmaps from a MessagingStyle message.
  */
 public class LocalImageResolver {
+    private static final String TAG = LocalImageResolver.class.getSimpleName();
 
     private static final int MAX_SAFE_ICON_SIZE_PX = 480;
 
@@ -60,11 +62,18 @@
 
     private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
             throws IOException {
-        InputStream input = context.getContentResolver().openInputStream(uri);
         BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
-        onlyBoundsOptions.inJustDecodeBounds = true;
-        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
-        input.close();
+        try (InputStream input = context.getContentResolver().openInputStream(uri)) {
+            if (input == null) {
+                throw new IllegalArgumentException();
+            }
+            onlyBoundsOptions.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
+        } catch (IllegalArgumentException iae) {
+            onlyBoundsOptions.outWidth = -1;
+            onlyBoundsOptions.outHeight = -1;
+            Log.e(TAG, "error loading image", iae);
+        }
         return onlyBoundsOptions;
     }
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f7370d6..08a9f48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -130,14 +130,15 @@
     public @interface CredentialType {}
 
     /**
-     * Flag provided to {@link #verifyCredential(LockscreenCredential, long, int, int)} . If set,
-     * the method will return the Gatekeeper Password in the {@link VerifyCredentialResponse}.
+     * 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_RETURN_GK_PW = 1 << 0;
+    public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
-            VERIFY_FLAG_RETURN_GK_PW
+            VERIFY_FLAG_REQUEST_GK_PW_HANDLE
     })
     public @interface VerifyFlag {}
 
@@ -409,16 +410,16 @@
     }
 
     /**
-     * With the Gatekeeper Password returned via {@link #verifyCredential(LockscreenCredential,
-     * int, int)}, request Gatekeeper to create a HardwareAuthToken wrapping the given
-     * challenge.
+     * 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 verifyGatekeeperPassword(@NonNull byte[] gatekeeperPassword,
+    public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
             long challenge, int userId) {
         try {
-            final VerifyCredentialResponse response = getLockSettings().verifyGatekeeperPassword(
-                    gatekeeperPassword, challenge, userId);
+            final VerifyCredentialResponse response = getLockSettings()
+                    .verifyGatekeeperPasswordHandle(gatekeeperPasswordHandle, challenge, userId);
             if (response == null) {
                 return VerifyCredentialResponse.ERROR;
             }
@@ -429,6 +430,14 @@
         }
     }
 
+    public void removeGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
+        try {
+            getLockSettings().removeGatekeeperPasswordHandle(gatekeeperPasswordHandle);
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to remove gatekeeper password handle", e);
+        }
+    }
+
     /**
      * Check to see if a credential matches the saved one.
      *
@@ -671,7 +680,7 @@
      */
     public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
             @NonNull LockscreenCredential savedCredential, int userHandle) {
-        if (!hasSecureLockScreen()) {
+        if (!hasSecureLockScreen() && newCredential.getType() != CREDENTIAL_TYPE_NONE) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
@@ -766,7 +775,7 @@
 
     /** Update the encryption password if it is enabled **/
     private void updateEncryptionPassword(final int type, final byte[] password) {
-        if (!hasSecureLockScreen()) {
+        if (!hasSecureLockScreen() && password != null && password.length != 0) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
@@ -1566,7 +1575,7 @@
      */
     public boolean setLockCredentialWithToken(@NonNull LockscreenCredential credential,
             long tokenHandle, byte[] token, int userHandle) {
-        if (!hasSecureLockScreen()) {
+        if (!hasSecureLockScreen() && credential.getType() != CREDENTIAL_TYPE_NONE) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index e09eb42..ab14634 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -49,7 +49,7 @@
     private final @ResponseCode int mResponseCode;
     private final int mTimeout;
     @Nullable private final byte[] mGatekeeperHAT;
-    @Nullable private final byte[] mGatekeeperPw;
+    private final long mGatekeeperPasswordHandle;
 
     public static final Parcelable.Creator<VerifyCredentialResponse> CREATOR
             = new Parcelable.Creator<VerifyCredentialResponse>() {
@@ -58,10 +58,10 @@
             final @ResponseCode int responseCode = source.readInt();
             final int timeout = source.readInt();
             final byte[] gatekeeperHAT = source.createByteArray();
-            final byte[] gatekeeperPassword = source.createByteArray();
+            long gatekeeperPasswordHandle = source.readLong();
 
             return new VerifyCredentialResponse(responseCode, timeout, gatekeeperHAT,
-                    gatekeeperPassword);
+                    gatekeeperPasswordHandle);
         }
 
         @Override
@@ -72,7 +72,7 @@
 
     public static class Builder {
         @Nullable private byte[] mGatekeeperHAT;
-        @Nullable private byte[] mGatekeeperPassword;
+        private long mGatekeeperPasswordHandle;
 
         /**
          * @param gatekeeperHAT Gatekeeper HardwareAuthToken, minted upon successful authentication.
@@ -82,8 +82,8 @@
             return this;
         }
 
-        public Builder setGatekeeperPassword(byte[] gatekeeperPassword) {
-            mGatekeeperPassword = gatekeeperPassword;
+        public Builder setGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
+            mGatekeeperPasswordHandle = gatekeeperPasswordHandle;
             return this;
         }
 
@@ -96,7 +96,7 @@
             return new VerifyCredentialResponse(RESPONSE_OK,
                     0 /* timeout */,
                     mGatekeeperHAT,
-                    mGatekeeperPassword);
+                    mGatekeeperPasswordHandle);
         }
     }
 
@@ -110,7 +110,7 @@
         return new VerifyCredentialResponse(RESPONSE_RETRY,
                 timeout,
                 null /* gatekeeperHAT */,
-                null /* gatekeeperPassword */);
+                0L /* gatekeeperPasswordHandle */);
     }
 
     /**
@@ -121,20 +121,20 @@
         return new VerifyCredentialResponse(RESPONSE_ERROR,
                 0 /* timeout */,
                 null /* gatekeeperHAT */,
-                null /* gatekeeperPassword */);
+                0L /* gatekeeperPasswordHandle */);
     }
 
     private VerifyCredentialResponse(@ResponseCode int responseCode, int timeout,
-            @Nullable byte[] gatekeeperHAT, @Nullable byte[] gatekeeperPassword) {
+            @Nullable byte[] gatekeeperHAT, long gatekeeperPasswordHandle) {
         mResponseCode = responseCode;
         mTimeout = timeout;
         mGatekeeperHAT = gatekeeperHAT;
-        mGatekeeperPw = gatekeeperPassword;
+        mGatekeeperPasswordHandle = gatekeeperPasswordHandle;
     }
 
     public VerifyCredentialResponse stripPayload() {
         return new VerifyCredentialResponse(mResponseCode, mTimeout,
-                null /* gatekeeperHAT */, null /* gatekeeperPassword */);
+                null /* gatekeeperHAT */, 0L /* gatekeeperPasswordHandle */);
     }
 
     @Override
@@ -142,7 +142,7 @@
         dest.writeInt(mResponseCode);
         dest.writeInt(mTimeout);
         dest.writeByteArray(mGatekeeperHAT);
-        dest.writeByteArray(mGatekeeperPw);
+        dest.writeLong(mGatekeeperPasswordHandle);
     }
 
     @Override
@@ -155,9 +155,12 @@
         return mGatekeeperHAT;
     }
 
-    @Nullable
-    public byte[] getGatekeeperPw() {
-        return mGatekeeperPw;
+    public long getGatekeeperPasswordHandle() {
+        return mGatekeeperPasswordHandle;
+    }
+
+    public boolean containsGatekeeperPasswordHandle() {
+        return mGatekeeperPasswordHandle != 0L;
     }
 
     public int getTimeout() {
@@ -176,7 +179,7 @@
     public String toString() {
         return "Response: " + mResponseCode
                 + ", GK HAT: " + (mGatekeeperHAT != null)
-                + ", GK PW: " + (mGatekeeperPw != null);
+                + ", GK PW: " + (mGatekeeperPasswordHandle != 0L);
     }
 
     public static VerifyCredentialResponse fromGateKeeperResponse(
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 9ad4cd9..859b40a 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -501,6 +501,15 @@
                              "Failed to read from parcel (error code %d)", err);
         return;
     }
+
+    // Update vendor descriptor cache if necessary
+    auto vendorId = metadata->getVendorId();
+    if ((vendorId != CAMERA_METADATA_INVALID_VENDOR_ID) &&
+            !VendorTagDescriptorCache::isVendorCachePresent(vendorId)) {
+        ALOGW("%s: Tag vendor id missing or cache not initialized, trying to update!",
+                __FUNCTION__);
+        CameraMetadata_setupGlobalVendorTagDescriptor(env, thiz);
+    }
 }
 
 static void CameraMetadata_writeToParcel(JNIEnv *env, jclass thiz, jobject parcel, jlong ptr) {
@@ -642,9 +651,7 @@
     CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(ptr);
     metadata_vendor_id_t vendorId = CAMERA_METADATA_INVALID_VENDOR_ID;
     if (metadata) {
-        const camera_metadata_t *metaBuffer = metadata->getAndLock();
-        vendorId = get_camera_metadata_vendor_id(metaBuffer);
-        metadata->unlock(metaBuffer);
+        vendorId = metadata->getVendorId();
     }
 
     int tagType = get_local_camera_metadata_tag_type_vendor_id(tag, vendorId);
@@ -673,9 +680,7 @@
     if (metadata) {
         sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache();
         if (cache.get()) {
-            const camera_metadata_t *metaBuffer = metadata->getAndLock();
-            metadata_vendor_id_t vendorId = get_camera_metadata_vendor_id(metaBuffer);
-            metadata->unlock(metaBuffer);
+            auto vendorId = metadata->getVendorId();
             cache->getVendorTagDescriptor(vendorId, &vTags);
         }
     }
@@ -703,10 +708,8 @@
         CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
         if (metadata == NULL) return NULL;
 
-        const camera_metadata_t *metaBuffer = metadata->getAndLock();
-        vendorId = get_camera_metadata_vendor_id(metaBuffer);
+        vendorId = metadata->getVendorId();
         cache->getVendorTagDescriptor(vendorId, &vTags);
-        metadata->unlock(metaBuffer);
         if (vTags.get() == nullptr) {
             return nullptr;
         }
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ecdba3f..a063820 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -56,8 +56,7 @@
     jfieldID scaleFactor;
     jfieldID touchableRegion;
     jfieldID visible;
-    jfieldID canReceiveKeys;
-    jfieldID hasFocus;
+    jfieldID focusable;
     jfieldID hasWallpaper;
     jfieldID paused;
     jfieldID trustedOverlay;
@@ -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,
@@ -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_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 5c045b6..7a5c383 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -20,6 +20,7 @@
 #include "android_media_AudioTrack.h"
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
@@ -251,7 +252,7 @@
                                            jint audioFormat, jint buffSizeInBytes, jint memoryMode,
                                            jintArray jSession, jlong nativeAudioTrack,
                                            jboolean offload, jint encapsulationMode,
-                                           jobject tunerConfiguration) {
+                                           jobject tunerConfiguration, jstring opPackageName) {
     ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d,"
           " nativeAudioTrack=0x%" PRIX64 ", offload=%d encapsulationMode=%d tuner=%p",
           jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes,
@@ -337,7 +338,8 @@
         }
 
         // create the native AudioTrack object
-        lpTrack = new AudioTrack();
+        ScopedUtfChars opPackageNameStr(env, opPackageName);
+        lpTrack = new AudioTrack(opPackageNameStr.c_str());
 
         // read the AudioAttributes values
         auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -371,23 +373,24 @@
         status_t status = NO_ERROR;
         switch (memoryMode) {
         case MODE_STREAM:
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    offload ? 0 : frameCount,
-                    offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    0,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC,
-                    offload ? &offloadInfo : NULL,
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
-
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, offload ? 0 : frameCount,
+                                  offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD
+                                          : AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0,    // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                        // to feed the AudioTrack
+                                  0,    // shared mem
+                                  true, // thread can call Java
+                                  sessionId, // audio session ID
+                                  offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK
+                                          : AudioTrack::TRANSFER_SYNC,
+                                  offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         case MODE_STATIC:
@@ -398,22 +401,22 @@
                 goto native_init_failure;
             }
 
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    frameCount,
-                    AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    lpJniStorage->mMemBase,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    AudioTrack::TRANSFER_SHARED,
-                    NULL,                         // default offloadInfo
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0, // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                     // to feed the AudioTrack
+                                  lpJniStorage->mMemBase, // shared mem
+                                  true,                   // thread can call Java
+                                  sessionId,              // audio session ID
+                                  AudioTrack::TRANSFER_SHARED,
+                                  NULL,   // default offloadInfo
+                                  -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         default:
@@ -1428,7 +1431,8 @@
         {"native_stop", "()V", (void *)android_media_AudioTrack_stop},
         {"native_pause", "()V", (void *)android_media_AudioTrack_pause},
         {"native_flush", "()V", (void *)android_media_AudioTrack_flush},
-        {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;)I",
+        {"native_setup",
+         "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;Ljava/lang/String;)I",
          (void *)android_media_AudioTrack_setup},
         {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
         {"native_release", "()V", (void *)android_media_AudioTrack_release},
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_util_Process.cpp b/core/jni/android_util_Process.cpp
index 23af70a..cbcbe7f 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -613,7 +613,7 @@
     }
 
     // Do not change sched policy cgroup after boot complete.
-    rc = androidSetThreadPriority(pid, pri, !boot_completed);
+    rc = androidSetThreadPriorityAndPolicy(pid, pri, !boot_completed);
     if (rc != 0) {
         if (rc == INVALID_OPERATION) {
             signalExceptionForPriorityError(env, errno, pid);
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 979a69a..9ed71ac0 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -22,11 +22,12 @@
 
 #include <nativehelper/JNIHelp.h>
 
+#include <android-base/stringprintf.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <input/InputTransport.h>
 #include <log/log.h>
 #include <utils/Looper.h>
-#include <utils/Vector.h>
-#include <input/InputTransport.h>
+#include <vector>
 #include "android_os_MessageQueue.h"
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
@@ -52,6 +53,21 @@
     jmethodID onBatchedInputEventPending;
 } gInputEventReceiverClassInfo;
 
+// Add prefix to the beginning of each line in 'str'
+static std::string addPrefix(std::string str, std::string_view prefix) {
+    str.insert(0, prefix); // insert at the beginning of the first line
+    const size_t prefixLength = prefix.length();
+    size_t pos = prefixLength; // just inserted prefix. start at the end of it
+    while (true) {             // process all newline characters in 'str'
+        pos = str.find('\n', pos);
+        if (pos == std::string::npos) {
+            break;
+        }
+        str.insert(pos + 1, prefix); // insert prefix just after the '\n' character
+        pos += prefixLength + 1;     // advance the position past the newly inserted prefix
+    }
+    return str;
+}
 
 class NativeInputEventReceiver : public LooperCallback {
 public:
@@ -64,6 +80,7 @@
     status_t finishInputEvent(uint32_t seq, bool handled);
     status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
             bool* outConsumedBatch);
+    std::string dump(const char* prefix);
 
 protected:
     virtual ~NativeInputEventReceiver();
@@ -80,7 +97,7 @@
     PreallocatedInputEventFactory mInputEventFactory;
     bool mBatchedInputEventPending;
     int mFdEvents;
-    Vector<Finish> mFinishQueue;
+    std::vector<Finish> mFinishQueue;
 
     void setFdEvents(int events);
 
@@ -128,7 +145,7 @@
     }
 
     status_t status = mInputConsumer.sendFinishedSignal(seq, handled);
-    if (status) {
+    if (status != OK) {
         if (status == WOULD_BLOCK) {
             if (kDebugDispatchCycle) {
                 ALOGD("channel '%s' ~ Could not send finished signal immediately.  "
@@ -137,7 +154,7 @@
             Finish finish;
             finish.seq = seq;
             finish.handled = handled;
-            mFinishQueue.add(finish);
+            mFinishQueue.push_back(finish);
             if (mFinishQueue.size() == 1) {
                 setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
             }
@@ -162,6 +179,9 @@
 }
 
 int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
+    // Allowed return values of this function as documented in LooperCallback::handleEvent
+    constexpr int REMOVE_CALLBACK = 0;
+    constexpr int KEEP_CALLBACK = 1;
     if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
         // This error typically occurs when the publisher has closed the input channel
         // as part of removing a window or finishing an IME session, in which case
@@ -170,41 +190,42 @@
             ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred.  "
                     "events=0x%x", getInputChannelName().c_str(), events);
         }
-        return 0; // remove the callback
+        return REMOVE_CALLBACK;
     }
 
     if (events & ALOOPER_EVENT_INPUT) {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
         mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
-        return status == OK || status == NO_MEMORY ? 1 : 0;
+        return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
     }
 
     if (events & ALOOPER_EVENT_OUTPUT) {
         for (size_t i = 0; i < mFinishQueue.size(); i++) {
-            const Finish& finish = mFinishQueue.itemAt(i);
+            const Finish& finish = mFinishQueue[i];
             status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
-            if (status) {
-                mFinishQueue.removeItemsAt(0, i);
+            if (status != OK) {
+                mFinishQueue.erase(mFinishQueue.begin(), mFinishQueue.begin() + i);
 
                 if (status == WOULD_BLOCK) {
                     if (kDebugDispatchCycle) {
                         ALOGD("channel '%s' ~ Sent %zu queued finish events; %zu left.",
-                                getInputChannelName().c_str(), i, mFinishQueue.size());
+                              getInputChannelName().c_str(), i, mFinishQueue.size());
                     }
-                    return 1; // keep the callback, try again later
+                    return KEEP_CALLBACK; // try again later
                 }
 
                 ALOGW("Failed to send finished signal on channel '%s'.  status=%d",
                         getInputChannelName().c_str(), status);
                 if (status != DEAD_OBJECT) {
                     JNIEnv* env = AndroidRuntime::getJNIEnv();
-                    String8 message;
-                    message.appendFormat("Failed to finish input event.  status=%d", status);
-                    jniThrowRuntimeException(env, message.string());
+                    std::string message =
+                            android::base::StringPrintf("Failed to finish input event.  status=%d",
+                                                        status);
+                    jniThrowRuntimeException(env, message.c_str());
                     mMessageQueue->raiseAndClearException(env, "finishInputEvent");
                 }
-                return 0; // remove the callback
+                return REMOVE_CALLBACK;
             }
         }
         if (kDebugDispatchCycle) {
@@ -213,12 +234,12 @@
         }
         mFinishQueue.clear();
         setFdEvents(ALOOPER_EVENT_INPUT);
-        return 1;
+        return KEEP_CALLBACK;
     }
 
     ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
             "events=0x%x", getInputChannelName().c_str(), events);
-    return 1;
+    return KEEP_CALLBACK;
 }
 
 status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
@@ -354,6 +375,23 @@
     }
 }
 
+std::string NativeInputEventReceiver::dump(const char* prefix) {
+    std::string out;
+    std::string consumerDump = addPrefix(mInputConsumer.dump(), "  ");
+    out = out + "mInputConsumer:\n" + consumerDump + "\n";
+
+    out += android::base::StringPrintf("mBatchedInputEventPending: %s\n",
+                                       toString(mBatchedInputEventPending));
+    out = out + "mFinishQueue:\n";
+    for (const Finish& finish : mFinishQueue) {
+        out += android::base::StringPrintf("  seq=%" PRIu32 " handled=%s\n", finish.seq,
+                                           toString(finish.handled));
+    }
+    if (mFinishQueue.empty()) {
+        out = out + "  <empty>\n";
+    }
+    return addPrefix(out, prefix);
+}
 
 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
         jobject inputChannelObj, jobject messageQueueObj) {
@@ -374,9 +412,10 @@
             receiverWeak, inputChannel, messageQueue);
     status_t status = receiver->initialize();
     if (status) {
-        String8 message;
-        message.appendFormat("Failed to initialize input event receiver.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
+        std::string message =
+                android::base::StringPrintf("Failed to initialize input event receiver.  status=%d",
+                                            status);
+        jniThrowRuntimeException(env, message.c_str());
         return 0;
     }
 
@@ -397,9 +436,9 @@
             reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
     status_t status = receiver->finishInputEvent(seq, handled);
     if (status && status != DEAD_OBJECT) {
-        String8 message;
-        message.appendFormat("Failed to finish input event.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
+        std::string message =
+                android::base::StringPrintf("Failed to finish input event.  status=%d", status);
+        jniThrowRuntimeException(env, message.c_str());
     }
 }
 
@@ -411,26 +450,31 @@
     status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
             &consumedBatch);
     if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
-        String8 message;
-        message.appendFormat("Failed to consume batched input event.  status=%d", status);
-        jniThrowRuntimeException(env, message.string());
+        std::string message =
+                android::base::StringPrintf("Failed to consume batched input event.  status=%d",
+                                            status);
+        jniThrowRuntimeException(env, message.c_str());
         return JNI_FALSE;
     }
     return consumedBatch ? JNI_TRUE : JNI_FALSE;
 }
 
+static jstring nativeDump(JNIEnv* env, jclass clazz, jlong receiverPtr, jstring prefix) {
+    sp<NativeInputEventReceiver> receiver =
+            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+    ScopedUtfChars prefixChars(env, prefix);
+    return env->NewStringUTF(receiver->dump(prefixChars.c_str()).c_str());
+}
 
 static const JNINativeMethod gMethods[] = {
-    /* name, signature, funcPtr */
-    { "nativeInit",
-            "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)J",
-            (void*)nativeInit },
-    { "nativeDispose", "(J)V",
-            (void*)nativeDispose },
-    { "nativeFinishInputEvent", "(JIZ)V",
-            (void*)nativeFinishInputEvent },
-    { "nativeConsumeBatchedInputEvents", "(JJ)Z",
-            (void*)nativeConsumeBatchedInputEvents },
+        /* name, signature, funcPtr */
+        {"nativeInit",
+         "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)J",
+         (void*)nativeInit},
+        {"nativeDispose", "(J)V", (void*)nativeDispose},
+        {"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
+        {"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
+        {"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},
 };
 
 int register_android_view_InputEventReceiver(JNIEnv* env) {
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 0d65bd1..6212bcb 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2345,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
@@ -2738,4 +2738,14 @@
     // CATEGORY: SETTINGS
     // OS: S
     SETTINGS_COLUMBUS = 1848;
+
+    // OPEN: Settings > Accessibility > Magnification > Settings > Magnification area > Magnification switch shortcut dialog
+    // CATEGORY: SETTINGS
+    // OS: S
+    DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = 1849;
+
+    // OPEN: Settings > Network & internet > Adaptive connectivity
+    // CATEGORY: SETTINGS
+    // OS: R QPR
+    ADAPTIVE_CONNECTIVITY_CATEGORY = 1850;
 }
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 32c1e4a..cdcb24b 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" />
@@ -3093,7 +3094,8 @@
         android:protectionLevel="signature" />
 
     <!-- Allows an application to be the status bar.  Currently used only by SystemUI.apk
-    @hide -->
+        @hide
+        @SystemApi -->
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
         android:protectionLevel="signature" />
 
@@ -5040,6 +5042,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/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-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-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-in/strings.xml b/core/res/res/values-in/strings.xml
index 085df65..f9e3e2f 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -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>
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-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-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 f99be88..5f2e4f9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4189,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.
@@ -4210,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/symbols.xml b/core/res/res/values/symbols.xml
index 040fad5..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 -->
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/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 896a534..0a751dd 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,14 +25,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
 
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
-import android.app.ActivityThread.ActivityClientRecord;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.ResourcesManager;
@@ -114,12 +114,11 @@
         final ActivityThread activityThread = activity.getActivityThread();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             activityThread.executeTransaction(newResumeTransaction(activity));
-            final ActivityClientRecord r = getActivityClientRecord(activity);
-            assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */,
-                    "test"));
+            assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
+                    true /* finalStateRequest */, "test"));
 
-            assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */,
-                    "test"));
+            assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
+                    false /* finalStateRequest */, "test"));
         });
     }
 
@@ -245,18 +244,20 @@
             newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
                     ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
             newerConfig.seq = seq + 2;
-            final ActivityClientRecord r = getActivityClientRecord(activity);
-            activityThread.updatePendingActivityConfiguration(r, newerConfig);
+            activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
+                    newerConfig);
 
             final Configuration olderConfig = new Configuration();
             olderConfig.orientation = orientation;
             olderConfig.seq = seq + 1;
 
-            activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    olderConfig, INVALID_DISPLAY);
             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
             assertEquals(olderConfig.orientation, activity.mConfig.orientation);
 
-            activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    newerConfig, INVALID_DISPLAY);
             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
             assertEquals(newerConfig.orientation, activity.mConfig.orientation);
         });
@@ -273,7 +274,7 @@
             config.seq = BASE_SEQ;
             config.orientation = ORIENTATION_PORTRAIT;
 
-            activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
                     config, INVALID_DISPLAY);
         });
 
@@ -334,8 +335,8 @@
             config.seq = BASE_SEQ;
             config.orientation = ORIENTATION_PORTRAIT;
 
-            final ActivityClientRecord r = getActivityClientRecord(activity);
-            activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    config, INVALID_DISPLAY);
         });
 
         final int numOfConfig = activity.mNumOfConfigChanges;
@@ -407,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
@@ -416,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);
@@ -437,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());
 
@@ -512,10 +560,9 @@
         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
         final ActivityThread activityThread = activity.getActivityThread();
-        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(r);
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
         });
 
         assertTrue(activity.pipRequested());
@@ -528,10 +575,9 @@
         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
         final ActivityThread activityThread = activity.getActivityThread();
-        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(r);
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
         });
 
         assertTrue(activity.pipRequested());
@@ -542,10 +588,9 @@
     public void testHandlePictureInPictureRequested_notOverridden() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
         final ActivityThread activityThread = activity.getActivityThread();
-        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            activityThread.handlePictureInPictureRequested(r);
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
         });
 
         assertTrue(activity.pipRequested());
@@ -554,9 +599,8 @@
     }
 
     /**
-     * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
-     * Configuration, int)} to try to push activity configuration to the activity for the given
-     * sequence number.
+     * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
+     * to try to push activity configuration to the activity for the given sequence number.
      * <p>
      * It uses orientation to push the configuration and it tries a different orientation if the
      * first attempt doesn't make through, to rule out the possibility that the previous
@@ -569,13 +613,13 @@
      */
     private int applyConfigurationChange(TestActivity activity, int seq) {
         final ActivityThread activityThread = activity.getActivityThread();
-        final ActivityClientRecord r = getActivityClientRecord(activity);
 
         final int numOfConfig = activity.mNumOfConfigChanges;
         Configuration config = new Configuration();
         config.orientation = ORIENTATION_PORTRAIT;
         config.seq = seq;
-        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+                INVALID_DISPLAY);
 
         if (activity.mNumOfConfigChanges > numOfConfig) {
             return config.seq;
@@ -584,17 +628,12 @@
         config = new Configuration();
         config.orientation = ORIENTATION_LANDSCAPE;
         config.seq = seq + 1;
-        activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+                INVALID_DISPLAY);
 
         return config.seq;
     }
 
-    private static ActivityClientRecord getActivityClientRecord(Activity activity) {
-        final ActivityThread thread = activity.getActivityThread();
-        final IBinder token = activity.getActivityToken();
-        return thread.getActivityClient(token);
-    }
-
     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
                 null, 0, new MergedConfiguration(), false /* preserveWindow */);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 6a0105c..3c32c71 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -32,12 +32,11 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Activity;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
@@ -226,7 +225,6 @@
         when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
         ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
         IBinder token = mock(IBinder.class);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
@@ -238,9 +236,9 @@
         mExecutor.execute(transaction);
 
         InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
-        inOrder.verify(callback1).execute(eq(mTransactionHandler), eq(token), any());
-        inOrder.verify(callback2).execute(eq(mTransactionHandler), eq(token), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(callback1, times(1)).execute(eq(mTransactionHandler), eq(token), any());
+        inOrder.verify(callback2, times(1)).execute(eq(mTransactionHandler), eq(token), any());
+        inOrder.verify(stateRequest, times(1)).execute(eq(mTransactionHandler), eq(token), any());
     }
 
     @Test
@@ -275,7 +273,7 @@
 
         // The launch transaction should not be executed because its token is in the
         // to-be-destroyed container.
-        verify(launchItem, never()).execute(any(), any(), any());
+        verify(launchItem, times(0)).execute(any(), any(), any());
 
         // After the destroy transaction has been executed, the token should be removed.
         mExecutor.execute(destroyTransaction);
@@ -284,8 +282,6 @@
 
     @Test
     public void testActivityResultRequiredStateResolution() {
-        when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
-
         PostExecItem postExecItem = new PostExecItem(ON_RESUME);
 
         IBinder token = mock(IBinder.class);
@@ -296,12 +292,12 @@
         // Verify resolution that should get to onPause
         mClientRecord.setState(ON_RESUME);
         mExecutor.executeCallbacks(transaction);
-        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+        verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
 
         // Verify resolution that should get to onStart
         mClientRecord.setState(ON_STOP);
         mExecutor.executeCallbacks(transaction);
-        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+        verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
     }
 
     @Test
@@ -437,38 +433,6 @@
                 mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME));
     }
 
-    @Test(expected = IllegalArgumentException.class)
-    public void testActivityItemNullRecordThrowsException() {
-        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
-        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
-        final IBinder token = mock(IBinder.class);
-        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
-                token /* activityToken */);
-        transaction.addCallback(activityItem);
-        when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
-
-        mExecutor.executeCallbacks(transaction);
-    }
-
-    @Test
-    public void testActivityItemExecute() {
-        final IBinder token = mock(IBinder.class);
-        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
-                token /* activityToken */);
-        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
-        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
-        transaction.addCallback(activityItem);
-        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
-        transaction.setLifecycleStateRequest(stateRequest);
-        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
-
-        mExecutor.execute(transaction);
-
-        final InOrder inOrder = inOrder(activityItem, stateRequest);
-        inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
-        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
-    }
-
     private static int[] shuffledArray(int[] inputArray) {
         final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
         Collections.shuffle(list);
@@ -525,13 +489,13 @@
 
         public static final Parcelable.Creator<StubItem> CREATOR =
                 new Parcelable.Creator<StubItem>() {
-            public StubItem createFromParcel(Parcel in) {
-                return new StubItem(in);
-            }
+                    public StubItem createFromParcel(Parcel in) {
+                        return new StubItem(in);
+                    }
 
-            public StubItem[] newArray(int size) {
-                return new StubItem[size];
-            }
-        };
+                    public StubItem[] newArray(int size) {
+                        return new StubItem[size];
+                    }
+                };
     }
 }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 72391f4..db127c6 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 
@@ -31,11 +32,22 @@
 
     @Test
     public void testEquals() {
-        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .setGeoDetectionEnabled(false)
+                .build();
+
+        TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration1)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration1)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -45,6 +57,20 @@
             assertEquals(one, two);
         }
 
+        builder2.setConfiguration(configuration2);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfiguration(configuration2);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
         builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -90,7 +116,12 @@
 
     @Test
     public void testParcelable() {
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
+        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+                .setConfiguration(configuration)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
@@ -105,4 +136,51 @@
         builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
     }
+
+    @Test
+    public void testApplyUpdate_permitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+                .setConfiguration(oldConfiguration)
+                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+                .build();
+        assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+                .setAutoDetectionEnabled(false)
+                .build();
+        assertEquals(expected, capabilities.applyUpdate(configChange));
+    }
+
+    @Test
+    public void testApplyUpdate_notPermitted() {
+        TimeZoneConfiguration oldConfiguration =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true)
+                        .build();
+        TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
+                .setConfiguration(oldConfiguration)
+                .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
+                .build();
+        assertEquals(oldConfiguration, capabilities.getConfiguration());
+
+        TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(false)
+                .build();
+
+        assertNull(capabilities.applyUpdate(configChange));
+    }
 }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index 00dc73e..faf908d 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -27,11 +27,14 @@
 
 public class TimeZoneConfigurationTest {
 
+    private static final int ARBITRARY_USER_ID = 9876;
+
     @Test
     public void testBuilder_copyConstructor() {
-        TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
-                .setAutoDetectionEnabled(true)
-                .setGeoDetectionEnabled(true);
+        TimeZoneConfiguration.Builder builder1 =
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                        .setAutoDetectionEnabled(true)
+                        .setGeoDetectionEnabled(true);
         TimeZoneConfiguration configuration1 = builder1.build();
 
         TimeZoneConfiguration configuration2 =
@@ -41,28 +44,28 @@
     }
 
     @Test
-    public void testIsComplete() {
-        TimeZoneConfiguration.Builder builder =
-                new TimeZoneConfiguration.Builder();
-        assertFalse(builder.build().isComplete());
+    public void testIntrospectionMethods() {
+        TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+        assertFalse(empty.isComplete());
+        assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
 
-        builder.setAutoDetectionEnabled(true);
-        assertFalse(builder.build().isComplete());
-
-        builder.setGeoDetectionEnabled(true);
-        assertTrue(builder.build().isComplete());
+        TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        assertTrue(completeConfig.isComplete());
+        assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
     }
 
     @Test
     public void testBuilder_mergeProperties() {
-        TimeZoneConfiguration configuration1 =
-                new TimeZoneConfiguration.Builder()
-                        .setAutoDetectionEnabled(true)
-                        .build();
+        TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionEnabled(true)
+                .build();
 
         {
             TimeZoneConfiguration mergedEmptyAnd1 =
-                    new TimeZoneConfiguration.Builder()
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                             .mergeProperties(configuration1)
                             .build();
             assertEquals(configuration1, mergedEmptyAnd1);
@@ -70,7 +73,7 @@
 
         {
             TimeZoneConfiguration configuration2 =
-                    new TimeZoneConfiguration.Builder()
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                             .setAutoDetectionEnabled(false)
                             .build();
 
@@ -87,14 +90,22 @@
     @Test
     public void testEquals() {
         TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         {
             TimeZoneConfiguration one = builder1.build();
             assertEquals(one, one);
         }
 
+        {
+            TimeZoneConfiguration.Builder differentUserBuilder =
+                    new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = differentUserBuilder.build();
+            assertNotEquals(one, two);
+        }
+
         TimeZoneConfiguration.Builder builder2 =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         {
             TimeZoneConfiguration one = builder1.build();
             TimeZoneConfiguration two = builder2.build();
@@ -148,7 +159,7 @@
     @Test
     public void testParcelable() {
         TimeZoneConfiguration.Builder builder =
-                new TimeZoneConfiguration.Builder();
+                new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
         assertRoundTripParcelable(builder.build());
 
         builder.setAutoDetectionEnabled(true);
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/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
index a35c604..0b51b2d 100644
--- a/core/tests/coretests/src/android/text/OWNERS
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -1,5 +1,4 @@
 set noparent
 
 siyamed@google.com
-nona@google.com
-clarabayarri@google.com
\ No newline at end of file
+nona@google.com
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index af02b7b..de128ad 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -746,6 +746,20 @@
             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));
         });
     }
 
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/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/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
index c577eef..fe7c944 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
@@ -29,6 +29,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 mainDexList:= \
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
@@ -60,6 +62,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 mainDexList2:= \
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
index da40940..3636c73 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
@@ -33,6 +33,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 include $(BUILD_PACKAGE)
 
 $(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
index 665e22d..67f1fa5 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
@@ -28,6 +28,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 mainDexList:= \
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
index c827fa8..33871e5 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
@@ -28,6 +28,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 mainDexList:= \
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
index 3d6ad7d..1b267ee 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
@@ -31,6 +31,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_EMMA_INSTRUMENT := false
+
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 
 include $(BUILD_PACKAGE)
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 3f2d0f1..1cdc75a 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -176,27 +176,27 @@
         }
 
         private void startActivity(ActivityClientRecord r) {
-            mThread.handleStartActivity(r, null /* pendingActions */);
+            mThread.handleStartActivity(r.token, null /* pendingActions */);
         }
 
         private void resumeActivity(ActivityClientRecord r) {
-            mThread.handleResumeActivity(r, true /* finalStateRequest */,
+            mThread.handleResumeActivity(r.token, true /* finalStateRequest */,
                     true /* isForward */, "test");
         }
 
         private void pauseActivity(ActivityClientRecord r) {
-            mThread.handlePauseActivity(r, false /* finished */,
+            mThread.handlePauseActivity(r.token, false /* finished */,
                     false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */,
                     "test");
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r, 0 /* configChanges */,
+            mThread.handleStopActivity(r.token, 0 /* configChanges */,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
         private void destroyActivity(ActivityClientRecord r) {
-            mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
+            mThread.handleDestroyActivity(r.token, true /* finishing */, 0 /* configChanges */,
                     false /* getNonConfigInstance */, "test");
         }
 
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/services.core.protolog.json b/data/etc/services.core.protolog.json
index 73296987..75eb7b6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -13,6 +13,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-2121056984": {
+      "message": "%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-2109936758": {
       "message": "removeAppToken make exiting: %s",
       "level": "VERBOSE",
@@ -37,12 +43,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-2049725903": {
+      "message": "Task back pressed on root taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-2039580386": {
       "message": "Attempted to add input method window with unknown token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-2029985709": {
+      "message": "setFocusedTask: taskId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-2024464438": {
       "message": "app-onAnimationFinished(): mOuter=%s",
       "level": "DEBUG",
@@ -73,6 +91,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
     },
+    "-1980468143": {
+      "message": "DisplayArea appeared name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "-1976930686": {
       "message": "Attempted to add Accessibility overlay window with bad token %s.  Aborting.",
       "level": "WARN",
@@ -91,6 +115,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1939861963": {
+      "message": "Create root task displayId=%d winMode=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-1939358269": {
       "message": "mRecentScreenshotAnimator finish",
       "level": "DEBUG",
@@ -115,6 +145,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1895337367": {
+      "message": "Delete root task display=%d winMode=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-1884933373": {
       "message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
       "level": "INFO",
@@ -139,6 +175,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1868048288": {
+      "message": "Updating to new configuration after starting activity.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityStarter.java"
+    },
     "-1862269827": {
       "message": "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -163,6 +205,24 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1810446914": {
+      "message": "Trying to update display configuration for system\/invalid process.",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "-1792633344": {
+      "message": "Register task organizer=%s uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
+    "-1791031393": {
+      "message": "Ensuring correct configuration: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1782453012": {
       "message": "Checking theme of starting window: 0x%x",
       "level": "VERBOSE",
@@ -211,6 +271,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-1699018375": {
+      "message": "Adding activity %s to task %s callers: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "-1698815688": {
       "message": "Resetting app token %s of replacing window marks.",
       "level": "DEBUG",
@@ -229,12 +295,30 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1638958146": {
+      "message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
+    },
     "-1632122349": {
       "message": "Changing surface while display frozen: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1630752478": {
+      "message": "removeLockedTask: removed %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
+    "-1598452494": {
+      "message": "activityDestroyedLocked: r=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_CONTAINERS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1596995693": {
       "message": "startAnimation",
       "level": "DEBUG",
@@ -307,6 +391,18 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1495062622": {
+      "message": "Can't report activity moved to display - client not running, activityRecord=%s, displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_SWITCH",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1492881555": {
+      "message": "Starting activity when config will change = %b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityStarter.java"
+    },
     "-1471946192": {
       "message": "Marking app token %s with replacing child windows.",
       "level": "DEBUG",
@@ -355,6 +451,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1364754753": {
+      "message": "Task vanished taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-1352076759": {
       "message": "Removing app token: %s",
       "level": "VERBOSE",
@@ -379,6 +481,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1305755880": {
+      "message": "Initial config: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-1292329638": {
       "message": "Added starting %s: startingWindow=%s startingView=%s",
       "level": "VERBOSE",
@@ -445,6 +553,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "-1155279885": {
+      "message": "Frontmost changed immersion: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_IMMERSIVE",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-1144293044": {
       "message": "SURFACE SET FREEZE LAYER: %s",
       "level": "INFO",
@@ -475,6 +589,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1115019498": {
+      "message": "Configuration & display unchanged in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1113134997": {
       "message": "Attempted to add application window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -559,12 +679,36 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-951939129": {
+      "message": "Unregister task organizer=%s uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
+    "-930893991": {
+      "message": "Set sync ready, syncId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
+    "-929676529": {
+      "message": "Configuration changes for %s, allChanges=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-928291778": {
       "message": "applyAnimation: anim=%s nextAppTransition=%d transit=%s Callers=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
+    "-927199900": {
+      "message": "Updating global configuration to: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-916108501": {
       "message": "Adding %s to %s",
       "level": "VERBOSE",
@@ -619,12 +763,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-846078709": {
+      "message": "Configuration doesn't matter in finishing %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-809771899": {
       "message": "findFocusedWindow: Reached focused app=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-804217032": {
+      "message": "Skipping config check (will change): %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-793346159": {
       "message": "New transit into wallpaper: %s",
       "level": "VERBOSE",
@@ -673,11 +829,17 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-714291355": {
-      "message": "Losing delayed focus: %s",
-      "level": "INFO",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    "-743431900": {
+      "message": "Configuration no differences in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-716565534": {
+      "message": "moveActivityStackToFront: unfocusable activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
     "-694710814": {
       "message": "Pausing rotation during drag",
@@ -739,6 +901,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-593535526": {
+      "message": "Binding proc %s with config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/am\/ActivityManagerService.java"
+    },
     "-583031528": {
       "message": "%s",
       "level": "INFO",
@@ -757,6 +925,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-548282316": {
+      "message": "setLockTaskMode: Locking to %s Callers=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-547111355": {
       "message": "hideIme Control target: %s ",
       "level": "DEBUG",
@@ -781,6 +955,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-503656156": {
+      "message": "Update process config of %s to new config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "-497620140": {
+      "message": "Transaction ready, syncId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "-496681057": {
       "message": "Attempted to get remove mode of a display that does not exist: %d",
       "level": "WARN",
@@ -799,6 +985,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "-449118559": {
+      "message": "Trying to update display configuration for invalid process, pid=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-445944810": {
       "message": "finish(%b): mCanceled=%b",
       "level": "DEBUG",
@@ -835,6 +1027,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
     },
+    "-401282500": {
+      "message": "destroyIfPossible: r=%s destroy returned removed=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_CONTAINERS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-395922585": {
       "message": "InsetsSource setWin %s",
       "level": "DEBUG",
@@ -907,18 +1105,42 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-317194205": {
+      "message": "clearLockedTasks: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-303497363": {
       "message": "reparent: moving activity=%s to task=%d at %d",
       "level": "INFO",
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-272719931": {
+      "message": "startLockTaskModeLocked: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "-260960989": {
+      "message": "Removing and adding activity %s to stack at top callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "-251259736": {
       "message": "No longer freezing: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-235225312": {
+      "message": "Skipping config check for initializing activity: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -943,6 +1165,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "-168799453": {
+      "message": "Allowing features %d:0x%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-167822951": {
       "message": "Attempted to add starting window to token with already existing starting window",
       "level": "WARN",
@@ -979,6 +1207,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-90559682": {
+      "message": "Config is skipping already pausing %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-87705714": {
       "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
       "level": "VERBOSE",
@@ -1159,6 +1393,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/BlackFrame.java"
     },
+    "174572959": {
+      "message": "DisplayArea info changed name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "184362060": {
       "message": "screenshotTask(%d): mCanceled=%b",
       "level": "DEBUG",
@@ -1201,6 +1441,12 @@
       "group": "WM_DEBUG_KEEP_SCREEN_ON",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "232317536": {
+      "message": "Set intercept back pressed on root=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "241961619": {
       "message": "Adding %s to %s",
       "level": "VERBOSE",
@@ -1219,6 +1465,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "251812577": {
+      "message": "Register display organizer=%s uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "254883724": {
       "message": "addWindowToken: Attempted to add binder token: %s for already created window token: %s displayId=%d",
       "level": "WARN",
@@ -1267,6 +1519,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "302969511": {
+      "message": "Task info changed taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "302992539": {
       "message": "addAnimation(%s)",
       "level": "DEBUG",
@@ -1303,6 +1561,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "355940361": {
+      "message": "Config is destroying non-running %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "371641947": {
       "message": "Window Manager Crash %s",
       "level": "WTF",
@@ -1315,18 +1579,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "374506950": {
+      "message": "Reporting activity moved to display, activityRecord=%s, displayId=%d, config=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_SWITCH",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "374972436": {
       "message": "performEnableScreen: Waiting for anim complete",
       "level": "INFO",
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "385096046": {
-      "message": "Delaying loss of focus...",
-      "level": "INFO",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "399841913": {
       "message": "SURFACE RECOVER DESTROY: %s",
       "level": "INFO",
@@ -1375,6 +1639,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "487621047": {
+      "message": "DisplayArea vanished name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "490877640": {
       "message": "onStackOrderChanged(): stack=%s",
       "level": "DEBUG",
@@ -1411,6 +1681,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "556758086": {
+      "message": "Applying new update lock state '%s' for %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_IMMERSIVE",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "557227556": {
       "message": "onAnimationFinished(): Notify animation finished:",
       "level": "DEBUG",
@@ -1531,12 +1807,6 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "676824470": {
-      "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-      "level": "ERROR",
-      "group": "TEST_GROUP",
-      "at": "com\/android\/server\/wm\/ProtoLogGroup.java"
-    },
     "685047360": {
       "message": "Resizing window %s",
       "level": "VERBOSE",
@@ -1561,6 +1831,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "715749922": {
+      "message": "Allowlisting %d:%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
+    "736692676": {
+      "message": "Config is relaunching %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "745391677": {
       "message": "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
       "level": "INFO",
@@ -1615,6 +1897,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "869266572": {
+      "message": "Removing activity %s from stack, reason= %s callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "873914452": {
       "message": "goodToGo()",
       "level": "DEBUG",
@@ -1633,6 +1921,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "906215061": {
+      "message": "Apply window transaction, syncId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "913494177": {
       "message": "removeAllWindowsIfPossible: removing win=%s",
       "level": "WARN",
@@ -1645,12 +1939,30 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "950074526": {
+      "message": "setLockTaskMode: Can't lock due to auth",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "954470154": {
       "message": "FORCED DISPLAY SCALING DISABLED",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "956374481": {
+      "message": "removeLockedTask: task=%s last task, reverting locktask mode. Callers=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
+    "969323241": {
+      "message": "Sending new config to %s, config: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "972354148": {
       "message": "\tcontainer=%s",
       "level": "DEBUG",
@@ -1663,12 +1975,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1040675582": {
+      "message": "Can't report activity configuration update - client not running, activityRecord=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1046922686": {
       "message": "requestScrollCapture: caught exception dispatching callback: %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1049367566": {
+      "message": "Sending to proc %s new config %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/WindowProcessController.java"
+    },
     "1051545910": {
       "message": "Exit animation finished in %s: remove=%b",
       "level": "VERBOSE",
@@ -1681,6 +2005,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
     },
+    "1088929964": {
+      "message": "onLockTaskPackagesUpdated: starting new locktask task=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1089714158": {
       "message": "  FREEZE %s: DESTROY",
       "level": "INFO",
@@ -1705,6 +2035,12 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/DisplayPolicy.java"
     },
+    "1149424314": {
+      "message": "Unregister display organizer=%s uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "1160771501": {
       "message": "Resize reasons for w=%s:  %s surfaceResized=%b configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b",
       "level": "VERBOSE",
@@ -1789,6 +2125,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1337596507": {
+      "message": "Sending to proc %s new compat %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/CompatModePackages.java"
+    },
     "1346895820": {
       "message": "ScreenRotation still animating: type: %d\nmDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s",
       "level": "VERBOSE",
@@ -1801,6 +2143,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "1360551978": {
+      "message": "Trying to update display configuration for non-existing displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1364498663": {
       "message": "notifyAppResumed: wasStopped=%b %s",
       "level": "VERBOSE",
@@ -1819,6 +2167,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
     },
+    "1401295262": {
+      "message": "Mode default, asking user",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1401700824": {
       "message": "Window drawn win=%s",
       "level": "DEBUG",
@@ -1927,6 +2281,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1522489371": {
+      "message": "moveActivityStackToFront: activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1525976603": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
@@ -1951,6 +2311,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1576607724": {
+      "message": "Report configuration: %s %s %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1577579529": {
       "message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b",
       "level": "ERROR",
@@ -1981,6 +2347,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1635062046": {
+      "message": "Skipping config check invisible: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1635462459": {
       "message": "onMovedByResize: Moving %s",
       "level": "DEBUG",
@@ -2023,6 +2395,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1679569477": {
+      "message": "Configuration doesn't matter not running %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1720229827": {
       "message": "Creating animation bounds layer",
       "level": "INFO",
@@ -2077,12 +2455,30 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1789603530": {
+      "message": "Removing activity %s hasSavedState=%b stateNotNeeded=%s finishing=%b state=%s callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1822843721": {
       "message": "Aborted starting %s: startingData=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1824105730": {
+      "message": "setLockTaskAuth: task=%s mLockTaskAuth=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
+    "1829094918": {
+      "message": "onLockTaskPackagesUpdated: removing %s mLockTaskAuth()=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "1831008694": {
       "message": "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s surfaceInsets=%s",
       "level": "DEBUG",
@@ -2131,6 +2527,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1918448345": {
+      "message": "Task appeared taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "1921821199": {
       "message": "Preserving %s until the new one is added",
       "level": "VERBOSE",
@@ -2161,6 +2563,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "1975793405": {
+      "message": "setFocusedStack: stackId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "1984470582": {
       "message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d",
       "level": "DEBUG",
@@ -2173,6 +2581,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowAnimator.java"
     },
+    "1995093920": {
+      "message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONFIGURATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "2016061474": {
       "message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s",
       "level": "VERBOSE",
@@ -2191,6 +2605,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "2022322588": {
+      "message": "Adding activity %s to stack to task %s callers: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "2022422429": {
       "message": "createAnimationAdapter(): container=%s",
       "level": "DEBUG",
@@ -2269,6 +2689,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "2134999275": {
+      "message": "moveActivityStackToFront: already on top, activity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "2137411379": {
       "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -2277,9 +2703,6 @@
     }
   },
   "groups": {
-    "TEST_GROUP": {
-      "tag": "WindowManagetProtoLogTest"
-    },
     "WM_DEBUG_ADD_REMOVE": {
       "tag": "WindowManager"
     },
@@ -2292,6 +2715,12 @@
     "WM_DEBUG_BOOT": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_CONFIGURATION": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_CONTAINERS": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_DRAW": {
       "tag": "WindowManager"
     },
@@ -2304,9 +2733,15 @@
     "WM_DEBUG_IME": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_IMMERSIVE": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_KEEP_SCREEN_ON": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_LOCKTASK": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_ORIENTATION": {
       "tag": "WindowManager"
     },
@@ -2325,9 +2760,15 @@
     "WM_DEBUG_STARTING_WINDOW": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_SWITCH": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_WINDOW_MOVEMENT": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_WINDOW_ORGANIZER": {
+      "tag": "WindowManager"
+    },
     "WM_ERROR": {
       "tag": "WindowManager"
     },
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index f410490..4f95a53 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -684,15 +684,13 @@
         return b;
     }
 
-    // FIXME: The maxTargetSdk should be R, once R is no longer set to
-    // CUR_DEVELOPMENT.
     /**
      * Creates a new immutable bitmap backed by ashmem which can efficiently
      * be passed between processes.
      *
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
             publicAlternatives = "Use {@link #asShared()} instead")
     public Bitmap createAshmemBitmap() {
         checkRecycled("Can't copy a recycled bitmap");
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/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/keystore/TEST_MAPPING b/keystore/TEST_MAPPING
new file mode 100644
index 0000000..0511967
--- /dev/null
+++ b/keystore/TEST_MAPPING
@@ -0,0 +1,74 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsKeystoreTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.RequiresDevice"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.SignatureTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.RsaSignaturePerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.RsaKeyGenPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.RsaCipherPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.MacTest#testLargeMsgKat"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.KeyPairGeneratorTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.KeyGeneratorTest#testHmacKeySupportedSizes"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.HmacMacPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.EcdsaSignaturePerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.EcKeyGenPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.DesCipherPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.CipherTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.AttestationPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.AndroidKeyStoreTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.AesCipherPerformanceTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.AESCipherNistCavpKatTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.DESedeECBPKCS7PaddingCipherTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.DESedeECBNoPaddingCipherTest"
+        },
+        {
+          "exclude-filter": "android.keystore.cts.DESedeECBPKCS7PaddingCipherTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsKeystoreTestCases"
+    }
+  ]
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 843b177..307b82e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -12,14 +12,88 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Begin ProtoLog
+java_library {
+    name: "wm_shell_protolog-groups",
+    srcs: [
+        "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
+        ":protolog-common-src",
+    ],
+}
+
+filegroup {
+    name: "wm_shell-sources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+}
+
+genrule {
+    name: "wm_shell_protolog_src",
+    srcs: [
+        ":wm_shell_protolog-groups",
+        ":wm_shell-sources",
+    ],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) transform-protolog-calls " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " +
+      "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " +
+      "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
+      "--loggroups-jar $(location :wm_shell_protolog-groups) " +
+      "--output-srcjar $(out) " +
+      "$(locations :wm_shell-sources)",
+    out: ["wm_shell_protolog.srcjar"],
+}
+
+genrule {
+    name: "generate-wm_shell_protolog.json",
+    srcs: [
+        ":wm_shell_protolog-groups",
+        ":wm_shell-sources",
+    ],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) generate-viewer-config " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
+      "--loggroups-jar $(location :wm_shell_protolog-groups) " +
+      "--viewer-conf $(out) " +
+      "$(locations :wm_shell-sources)",
+    out: ["wm_shell_protolog.json"],
+}
+
+filegroup {
+    name: "wm_shell_protolog.json",
+    srcs: ["res/raw/wm_shell_protolog.json"],
+}
+
+genrule {
+    name: "checked-wm_shell_protolog.json",
+    srcs: [
+        ":generate-wm_shell_protolog.json",
+        ":wm_shell_protolog.json",
+    ],
+    cmd: "cp $(location :generate-wm_shell_protolog.json) $(out) && " +
+      "{ ! (diff $(out) $(location :wm_shell_protolog.json) | grep -q '^<') || " +
+      "{ echo -e '\\n\\n################################################################\\n#\\n" +
+      "#  ERROR: ProtoLog viewer config is stale.  To update it, run:\\n#\\n" +
+      "#  cp $(location :generate-wm_shell_protolog.json) " +
+      "$(location :wm_shell_protolog.json)\\n#\\n" +
+      "################################################################\\n\\n' >&2 && false; } }",
+    out: ["wm_shell_protolog.json"],
+}
+// End ProtoLog
+
 android_library {
     name: "WindowManager-Shell",
     srcs: [
-        "src/**/*.java",
+        ":wm_shell_protolog_src",
         "src/**/I*.aidl",
     ],
     resource_dirs: [
         "res",
     ],
+    static_libs: [
+        "protolog-lib",
+    ],
     manifest: "AndroidManifest.xml",
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu_activity.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/layout/pip_menu_activity.xml
rename to libs/WindowManager/Shell/res/layout/pip_menu.xml
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
new file mode 100644
index 0000000..7242793
--- /dev/null
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -0,0 +1,46 @@
+{
+  "version": "1.0.0",
+  "messages": {
+    "-1340279385": {
+      "message": "Remove listener=%s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
+    "-880817403": {
+      "message": "Task vanished taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
+    "-460572385": {
+      "message": "Task appeared taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
+    "-242812822": {
+      "message": "Add listener for modes=%s listener=%s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
+    "157713005": {
+      "message": "Task info changed taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
+    "980952660": {
+      "message": "Task root back pressed taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    }
+  },
+  "groups": {
+    "WM_SHELL_TASK_ORG": {
+      "tag": "WindowManagerShell"
+    }
+  }
+}
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..ea9576a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.app.WindowConfiguration;
+import android.content.Context;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.protolog.ShellProtoLogImpl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * 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) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Add listener for modes=%s listener=%s",
+                Arrays.toString(windowingModes), listener);
+        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) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Remove listener=%s", listener);
+        for (int i = 0; i < mListenersByWindowingMode.size(); i++) {
+            mListenersByWindowingMode.valueAt(i).remove(listener);
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task appeared taskId=%d",
+                taskInfo.taskId);
+        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) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task info changed taskId=%d",
+                taskInfo.taskId);
+        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) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task root back pressed taskId=%d",
+                taskInfo.taskId);
+        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) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task vanished taskId=%d",
+                taskInfo.taskId);
+        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/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
new file mode 100644
index 0000000..ae09754
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.protolog;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+/**
+ * Defines logging groups for ProtoLog.
+ *
+ * This file is used by the ProtoLogTool to generate optimized logging code.
+ */
+public enum ShellProtoLogGroup implements IProtoLogGroup {
+    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_SHELL),
+    TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
+
+    private final boolean mEnabled;
+    private volatile boolean mLogToProto;
+    private volatile boolean mLogToLogcat;
+    private final String mTag;
+
+    /**
+     * @param enabled     set to false to exclude all log statements for this group from
+     *                    compilation,
+     *                    they will not be available in runtime.
+     * @param logToProto  enable binary logging for the group
+     * @param logToLogcat enable text logging for the group
+     * @param tag         name of the source of the logged message
+     */
+    ShellProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+        this.mEnabled = enabled;
+        this.mLogToProto = logToProto;
+        this.mLogToLogcat = logToLogcat;
+        this.mTag = tag;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    @Override
+    public boolean isLogToProto() {
+        return mLogToProto;
+    }
+
+    @Override
+    public boolean isLogToLogcat() {
+        return mLogToLogcat;
+    }
+
+    @Override
+    public boolean isLogToAny() {
+        return mLogToLogcat || mLogToProto;
+    }
+
+    @Override
+    public String getTag() {
+        return mTag;
+    }
+
+    @Override
+    public void setLogToProto(boolean logToProto) {
+        this.mLogToProto = logToProto;
+    }
+
+    @Override
+    public void setLogToLogcat(boolean logToLogcat) {
+        this.mLogToLogcat = logToLogcat;
+    }
+
+    private static class Consts {
+        private static final String TAG_WM_SHELL = "WindowManagerShell";
+
+        private static final boolean ENABLE_DEBUG = true;
+        private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
new file mode 100644
index 0000000..6a925e7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.protolog;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.BaseProtoLogImpl;
+import com.android.internal.protolog.ProtoLogViewerConfigReader;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.wm.shell.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.json.JSONException;
+
+
+/**
+ * A service for the ProtoLog logging system.
+ */
+public class ShellProtoLogImpl extends BaseProtoLogImpl {
+    private static final String TAG = "ProtoLogImpl";
+    private static final int BUFFER_CAPACITY = 1024 * 1024;
+    // TODO: Get the right path for the proto log file when we initialize the shell components
+    private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+
+    private static ShellProtoLogImpl sServiceInstance = null;
+
+    private final PrintWriter mSystemOutWriter;
+
+    static {
+        addLogGroupEnum(ShellProtoLogGroup.values());
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+                args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
+    public static boolean isEnabled(IProtoLogGroup group) {
+        return group.isLogToLogcat()
+                || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
+    }
+
+    /**
+     * Returns the single instance of the ProtoLogImpl singleton class.
+     */
+    public static synchronized ShellProtoLogImpl getSingleInstance() {
+        if (sServiceInstance == null) {
+            sServiceInstance = new ShellProtoLogImpl();
+        }
+        return sServiceInstance;
+    }
+
+    public void startTextLogging(Context context, String... groups) {
+        try {
+            mViewerConfig.loadViewerConfig(
+                    context.getResources().openRawResource(R.raw.wm_shell_protolog));
+            setLogging(true /* setTextLogging */, true, mSystemOutWriter, groups);
+        } catch (IOException e) {
+            Log.i(TAG, "Unable to load log definitions: IOException while reading "
+                    + "wm_shell_protolog. " + e);
+        } catch (JSONException e) {
+            Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
+                    + "wm_shell_protolog. " + e);
+        }
+    }
+
+    public void stopTextLogging(String... groups) {
+        setLogging(true /* setTextLogging */, false, mSystemOutWriter, groups);
+    }
+
+    private ShellProtoLogImpl() {
+        super(new File(LOG_FILENAME), null, BUFFER_CAPACITY,
+                new ProtoLogViewerConfigReader());
+        mSystemOutWriter = new PrintWriter(System.out, true);
+    }
+}
+
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/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/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 892d43a..473dc53d 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -524,6 +524,7 @@
         // Next greater multiple of SKLITEDL_PAGE.
         fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1);
         fBytes.realloc(fReserved);
+        LOG_ALWAYS_FATAL_IF(fBytes.get() == nullptr, "realloc(%zd) failed", fReserved);
     }
     SkASSERT(fUsed + skip <= fReserved);
     auto op = (T*)(fBytes.get() + fUsed);
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/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/Picture.cpp b/libs/hwui/jni/Picture.cpp
index d1b9521..8e4203c 100644
--- a/libs/hwui/jni/Picture.cpp
+++ b/libs/hwui/jni/Picture.cpp
@@ -111,7 +111,7 @@
 
     SkPictureRecorder reRecorder;
 
-    SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0);
+    SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight);
     mRecorder->partialReplay(canvas);
     return reRecorder.finishRecordingAsPicture();
 }
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index e76aace..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,54 +186,74 @@
     #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);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,12 +271,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-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));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -262,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     },
 };
@@ -293,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 fc594da..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,
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/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..76ec078 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,20 +359,7 @@
     this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
 
     mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
-
-    // create the command pool for the command buffers
-    if (VK_NULL_HANDLE == mCommandPool) {
-        VkCommandPoolCreateInfo commandPoolInfo;
-        memset(&commandPoolInfo, 0, sizeof(VkCommandPoolCreateInfo));
-        commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
-        // this needs to be on the render queue
-        commandPoolInfo.queueFamilyIndex = mGraphicsQueueIndex;
-        commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
-        SkDEBUGCODE(VkResult res =)
-                mCreateCommandPool(mDevice, &commandPoolInfo, nullptr, &mCommandPool);
-        SkASSERT(VK_SUCCESS == res);
-    }
-    LOG_ALWAYS_FATAL_IF(mCommandPool == VK_NULL_HANDLE);
+    mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
 
     mGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue);
 
@@ -369,7 +368,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 +381,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..75c05b8 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,9 +162,9 @@
 
     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;
 
     // Variables saved to populate VkFunctorInitParams.
     static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0);
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/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index fcc64fd..f77ca2a 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -73,7 +73,7 @@
 
     // Test picture recording.
     SkPictureRecorder recorder;
-    SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
+    SkCanvas* skPicCanvas = recorder.beginRecording(1, 1);
     SkiaCanvas picCanvas(skPicCanvas);
     picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
     sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
@@ -104,7 +104,7 @@
 
     // Create a picture canvas.
     SkPictureRecorder recorder;
-    SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
+    SkCanvas* skPicCanvas = recorder.beginRecording(1, 1);
     SkiaCanvas picCanvas(skPicCanvas);
     state = picCanvas.captureCanvasState();
 
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/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 80b555b..45da008 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -168,7 +168,7 @@
         updatePointerLocked();
     } else {
         mLocked.pointerFadeDirection = -1;
-        mContext.startAnimation();
+        startAnimationLocked();
     }
 }
 
@@ -185,7 +185,7 @@
         updatePointerLocked();
     } else {
         mLocked.pointerFadeDirection = 1;
-        mContext.startAnimation();
+        startAnimationLocked();
     }
 }
 
@@ -312,10 +312,9 @@
     updatePointerLocked();
 }
 
-bool MouseCursorController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+bool MouseCursorController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
     nsecs_t frameDelay = timestamp - mContext.getAnimationTime();
-
-    std::scoped_lock lock(mLock);
+    bool keepAnimating = false;
 
     // Animate pointer fade.
     if (mLocked.pointerFadeDirection < 0) {
@@ -337,13 +336,10 @@
         }
         updatePointerLocked();
     }
-
     return keepAnimating;
 }
 
-bool MouseCursorController::doBitmapAnimation(nsecs_t timestamp) {
-    std::scoped_lock lock(mLock);
-
+bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
     std::map<int32_t, PointerAnimation>::const_iterator iter =
             mLocked.animationResources.find(mLocked.requestedPointerType);
     if (iter == mLocked.animationResources.end()) {
@@ -364,7 +360,6 @@
 
         spriteController->closeTransaction();
     }
-
     // Keep animating.
     return true;
 }
@@ -399,7 +394,7 @@
                 if (anim_iter != mLocked.animationResources.end()) {
                     mLocked.animationFrameIndex = 0;
                     mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
-                    mContext.startAnimation();
+                    startAnimationLocked();
                 }
                 mLocked.pointerSprite->setIcon(iter->second);
             } else {
@@ -457,4 +452,38 @@
     return mLocked.resourcesLoaded;
 }
 
+bool MouseCursorController::doAnimations(nsecs_t timestamp) {
+    std::scoped_lock lock(mLock);
+    bool keepFading = doFadingAnimationLocked(timestamp);
+    bool keepBitmap = doBitmapAnimationLocked(timestamp);
+    bool keepAnimating = keepFading || keepBitmap;
+    if (!keepAnimating) {
+        /*
+         * We know that this callback will be removed before another
+         * is added. mLock in PointerAnimator will not be released
+         * until after this is removed, and adding another callback
+         * requires that lock. Thus it's safe to set mLocked.animating
+         * here.
+         */
+        mLocked.animating = false;
+    }
+    return keepAnimating;
+}
+
+void MouseCursorController::startAnimationLocked() REQUIRES(mLock) {
+    using namespace std::placeholders;
+
+    if (mLocked.animating) {
+        return;
+    }
+    mLocked.animating = true;
+
+    std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
+    /*
+     * Using -1 for displayId here to avoid removing the callback
+     * if a TouchSpotController with the same display is removed.
+     */
+    mContext.addAnimationCallback(-1, func);
+}
+
 } // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 448165b..e6dfc4c 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -25,6 +25,7 @@
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <vector>
@@ -61,8 +62,7 @@
     void getAdditionalMouseResources();
     bool isViewportValid();
 
-    bool doBitmapAnimation(nsecs_t timestamp);
-    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+    bool doAnimations(nsecs_t timestamp);
 
     bool resourcesLoaded();
 
@@ -96,6 +96,8 @@
 
         int32_t buttonState;
 
+        bool animating{false};
+
     } mLocked GUARDED_BY(mLock);
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
@@ -104,6 +106,11 @@
     void updatePointerLocked();
 
     void loadResourcesLocked(bool getAdditionalMouseResources);
+
+    bool doBitmapAnimationLocked(nsecs_t timestamp);
+    bool doFadingAnimationLocked(nsecs_t timestamp);
+
+    void startAnimationLocked();
 };
 
 } // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 14c96ce..8f04cfb 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -57,7 +57,6 @@
 
     controller->mContext.setHandlerController(controller);
     controller->mContext.setCallbackController(controller);
-    controller->mContext.initializeDisplayEventReceiver();
     return controller;
 }
 
@@ -189,24 +188,6 @@
     mCursorController.setCustomPointerIcon(icon);
 }
 
-void PointerController::doAnimate(nsecs_t timestamp) {
-    std::scoped_lock lock(mLock);
-
-    mContext.setAnimationPending(false);
-
-    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) {
-        mContext.startAnimation();
-    }
-}
-
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
@@ -221,6 +202,11 @@
     for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
         int32_t displayID = it->first;
         if (!displayIdSet.count(displayID)) {
+            /*
+             * Ensures that an in-progress animation won't dereference
+             * a null pointer to TouchSpotController.
+             */
+            mContext.removeAnimationCallback(displayID);
             it = mLocked.spotControllers.erase(it);
         } else {
             ++it;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 1f561da..827fcf1 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -70,7 +70,6 @@
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
-    void doAnimate(nsecs_t timestamp);
     void reloadPointerResources();
     void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
 
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 2d7e22b..f30e8d8 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -38,10 +38,10 @@
         mSpriteController(spriteController),
         mHandler(new MessageHandler()),
         mCallback(new LooperCallback()),
-        mController(controller) {
+        mController(controller),
+        mAnimator(*this) {
     std::scoped_lock lock(mLock);
     mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
-    mLocked.animationPending = false;
 }
 
 PointerControllerContext::~PointerControllerContext() {
@@ -57,15 +57,6 @@
     }
 }
 
-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();
@@ -85,14 +76,8 @@
     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;
+nsecs_t PointerControllerContext::getAnimationTime() REQUIRES(mAnimator.mLock) {
+    return mAnimator.getAnimationTimeLocked();
 }
 
 void PointerControllerContext::setHandlerController(std::shared_ptr<PointerController> controller) {
@@ -112,31 +97,8 @@
     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);
-    }
+    mAnimator.handleVsyncEvents();
 }
 
 void PointerControllerContext::MessageHandler::handleMessage(const Message& message) {
@@ -176,4 +138,91 @@
     return 1; // keep the callback
 }
 
+void PointerControllerContext::addAnimationCallback(int32_t displayId,
+                                                    std::function<bool(nsecs_t)> callback) {
+    mAnimator.addCallback(displayId, callback);
+}
+
+void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+    mAnimator.removeCallback(displayId);
+}
+
+PointerControllerContext::PointerAnimator::PointerAnimator(PointerControllerContext& context)
+      : mContext(context) {
+    initializeDisplayEventReceiver();
+}
+
+void PointerControllerContext::PointerAnimator::initializeDisplayEventReceiver() {
+    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+        mContext.mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+                                Looper::EVENT_INPUT, mContext.mCallback, nullptr);
+    } else {
+        ALOGE("Failed to initialize DisplayEventReceiver.");
+    }
+}
+
+void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+                                                            std::function<bool(nsecs_t)> callback) {
+    std::scoped_lock lock(mLock);
+    mLocked.callbacks[displayId] = callback;
+    startAnimationLocked();
+}
+
+void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+    std::scoped_lock lock(mLock);
+    auto it = mLocked.callbacks.find(displayId);
+    if (it == mLocked.callbacks.end()) {
+        return;
+    }
+    mLocked.callbacks.erase(it);
+}
+
+void PointerControllerContext::PointerAnimator::handleVsyncEvents() {
+    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) {
+        std::scoped_lock lock(mLock);
+        mLocked.animationPending = false;
+        handleCallbacksLocked(timestamp);
+    }
+}
+
+nsecs_t PointerControllerContext::PointerAnimator::getAnimationTimeLocked() REQUIRES(mLock) {
+    return mLocked.animationTime;
+}
+
+void PointerControllerContext::PointerAnimator::startAnimationLocked() REQUIRES(mLock) {
+    if (!mLocked.animationPending) {
+        mLocked.animationPending = true;
+        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDisplayEventReceiver.requestNextVsync();
+    }
+}
+
+void PointerControllerContext::PointerAnimator::handleCallbacksLocked(nsecs_t timestamp)
+        REQUIRES(mLock) {
+    for (auto it = mLocked.callbacks.begin(); it != mLocked.callbacks.end();) {
+        bool keepCallback = it->second(timestamp);
+        if (!keepCallback) {
+            it = mLocked.callbacks.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+    if (!mLocked.callbacks.empty()) {
+        startAnimationLocked();
+    }
+}
+
 } // namespace android
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 92e1bda..98073fe 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -26,6 +26,7 @@
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <vector>
@@ -35,6 +36,8 @@
 namespace android {
 
 class PointerController;
+class MouseCursorController;
+class TouchSpotController;
 
 /*
  * Pointer resources.
@@ -96,7 +99,6 @@
     void startAnimation();
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
 
-    void setAnimationPending(bool animationPending);
     nsecs_t getAnimationTime();
 
     void clearSpotsByDisplay(int32_t displayId);
@@ -107,9 +109,11 @@
     sp<PointerControllerPolicyInterface> getPolicy();
     sp<SpriteController> getSpriteController();
 
-    void initializeDisplayEventReceiver();
     void handleDisplayEvents();
 
+    void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
+    void removeAnimationCallback(int32_t displayId);
+
     class MessageHandler : public virtual android::MessageHandler {
     public:
         enum {
@@ -127,22 +131,47 @@
     };
 
 private:
+    class PointerAnimator {
+    public:
+        PointerAnimator(PointerControllerContext& context);
+
+        void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
+        void removeCallback(int32_t displayId);
+        void handleVsyncEvents();
+        nsecs_t getAnimationTimeLocked();
+
+        mutable std::mutex mLock;
+
+    private:
+        struct Locked {
+            bool animationPending{false};
+            nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
+
+            std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+        } mLocked GUARDED_BY(mLock);
+
+        DisplayEventReceiver mDisplayEventReceiver;
+
+        PointerControllerContext& mContext;
+
+        void initializeDisplayEventReceiver();
+        void startAnimationLocked();
+        void handleCallbacksLocked(nsecs_t timestamp);
+    };
+
     sp<PointerControllerPolicyInterface> mPolicy;
     sp<Looper> mLooper;
     sp<SpriteController> mSpriteController;
     sp<MessageHandler> mHandler;
     sp<LooperCallback> mCallback;
 
-    DisplayEventReceiver mDisplayEventReceiver;
-
     PointerController& mController;
 
+    PointerAnimator mAnimator;
+
     mutable std::mutex mLock;
 
     struct Locked {
-        bool animationPending;
-        nsecs_t animationTime;
-
         InactivityTimeout inactivityTimeout;
     } mLocked GUARDED_BY(mLock);
 
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index c7430ce..f7c685f 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -142,7 +142,8 @@
 }
 
 TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t id,
-                                                                       std::vector<Spot*>& spots) {
+                                                                       std::vector<Spot*>& spots)
+        REQUIRES(mLock) {
     // Remove spots until we have fewer than MAX_SPOTS remaining.
     while (spots.size() >= MAX_SPOTS) {
         Spot* spot = removeFirstFadingSpotLocked(spots);
@@ -186,14 +187,13 @@
     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();
+        startAnimationLocked();
     }
 }
 
@@ -209,8 +209,24 @@
     mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
 }
 
-bool TouchSpotController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+bool TouchSpotController::doAnimations(nsecs_t timestamp) {
     std::scoped_lock lock(mLock);
+    bool keepAnimating = doFadingAnimationLocked(timestamp);
+    if (!keepAnimating) {
+        /*
+         * We know that this callback will be removed before another
+         * is added. mLock in PointerAnimator will not be released
+         * until after this is removed, and adding another callback
+         * requires that lock. Thus it's safe to set mLocked.animating
+         * here.
+         */
+        mLocked.animating = false;
+    }
+    return keepAnimating;
+}
+
+bool TouchSpotController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
+    bool keepAnimating = false;
     nsecs_t animationTime = mContext.getAnimationTime();
     nsecs_t frameDelay = timestamp - animationTime;
     size_t numSpots = mLocked.displaySpots.size();
@@ -233,4 +249,16 @@
     return keepAnimating;
 }
 
+void TouchSpotController::startAnimationLocked() REQUIRES(mLock) {
+    using namespace std::placeholders;
+
+    if (mLocked.animating) {
+        return;
+    }
+    mLocked.animating = true;
+
+    std::function<bool(nsecs_t)> func = std::bind(&TouchSpotController::doAnimations, this, _1);
+    mContext.addAnimationCallback(mDisplayId, func);
+}
+
 } // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index f3b3550..703de36 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -17,6 +17,8 @@
 #ifndef _UI_TOUCH_SPOT_CONTROLLER_H
 #define _UI_TOUCH_SPOT_CONTROLLER_H
 
+#include <functional>
+
 #include "PointerControllerContext.h"
 
 namespace android {
@@ -34,7 +36,7 @@
     void clearSpots();
 
     void reloadSpotResources();
-    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+    bool doAnimations(nsecs_t timestamp);
 
 private:
     struct Spot {
@@ -76,6 +78,8 @@
         std::vector<Spot*> displaySpots;
         std::vector<sp<Sprite>> recycledSprites;
 
+        bool animating{false};
+
     } mLocked GUARDED_BY(mLock);
 
     Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
@@ -84,6 +88,8 @@
     void releaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseAllSpotsLocked();
+    bool doFadingAnimationLocked(nsecs_t timestamp);
+    void startAnimationLocked();
 };
 
 } // namespace android
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1803027..6fc702e 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -29,6 +29,8 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -215,6 +217,7 @@
      * @see #EXTRA_PROVIDER_ENABLED
      * @see #isProviderEnabled(String)
      */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED";
 
     /**
@@ -243,6 +246,7 @@
      * @see #EXTRA_LOCATION_ENABLED
      * @see #isLocationEnabled()
      */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String MODE_CHANGED_ACTION = "android.location.MODE_CHANGED";
 
     /**
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/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1c0a526..de2a7b2 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -807,7 +807,8 @@
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                 sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
-                offload, encapsulationMode, tunerConfiguration);
+                offload, encapsulationMode, tunerConfiguration,
+                getCurrentOpPackageName());
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
             return; // with mState == STATE_UNINITIALIZED
@@ -893,7 +894,8 @@
                     nativeTrackInJavaObj,
                     false /*offload*/,
                     ENCAPSULATION_MODE_NONE,
-                    null /* tunerConfiguration */);
+                    null /* tunerConfiguration */,
+                    "" /* opPackagename */);
             if (initResult != SUCCESS) {
                 loge("Error code "+initResult+" when initializing AudioTrack.");
                 return; // with mState == STATE_UNINITIALIZED
@@ -4062,7 +4064,8 @@
             Object /*AudioAttributes*/ attributes,
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
             int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack,
-            boolean offload, int encapsulationMode, Object tunerConfiguration);
+            boolean offload, int encapsulationMode, Object tunerConfiguration,
+            @NonNull String opPackageName);
 
     private native final void native_finalize();
 
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 49e4160..36ae3ec 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -672,7 +672,8 @@
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
          */
-        native_setup(new WeakReference<MediaPlayer>(this));
+        native_setup(new WeakReference<MediaPlayer>(this),
+                getCurrentOpPackageName());
 
         baseRegisterPlayer();
     }
@@ -2378,7 +2379,7 @@
     private native final int native_setMetadataFilter(Parcel request);
 
     private static native final void native_init();
-    private native final void native_setup(Object mediaplayer_this);
+    private native void native_setup(Object mediaplayerThis, @NonNull String opPackageName);
     private native final void native_finalize();
 
     /**
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index e1bff03..4e2ae5c 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -36,7 +36,10 @@
 import java.io.FileNotFoundException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -94,11 +97,17 @@
  TODO(hkuang): Clarify whether supports framerate conversion.
  @hide
  */
-public final class MediaTranscodeManager {
+public final class MediaTranscodeManager implements AutoCloseable {
     private static final String TAG = "MediaTranscodeManager";
 
     private static final String MEDIA_TRANSCODING_SERVICE = "media.transcoding";
 
+    /** Maximum number of retry to connect to the service. */
+    private static final int CONNECT_SERVICE_RETRY_COUNT = 100;
+
+    /** Interval between trying to reconnect to the service. */
+    private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40;
+
     /**
      * Default transcoding type.
      * @hide
@@ -117,6 +126,25 @@
      */
     public static final int TRANSCODING_TYPE_IMAGE = 2;
 
+    @Override
+    public void close() throws Exception {
+        release();
+    }
+
+    /**
+     * Releases the MediaTranscodeManager.
+     */
+    //TODO(hkuang): add test for it.
+    private void release() throws Exception {
+        synchronized (mLock) {
+            if (mTranscodingClient != null) {
+                mTranscodingClient.unregister();
+            } else {
+                throw new UnsupportedOperationException("Failed to release");
+            }
+        }
+    }
+
     /** @hide */
     @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
             TRANSCODING_TYPE_UNKNOWN,
@@ -181,10 +209,12 @@
     private final String mPackageName;
     private final int mPid;
     private final int mUid;
-    private final ExecutorService mCallbackExecutor = Executors.newSingleThreadExecutor();
-    private static MediaTranscodeManager sMediaTranscodeManager;
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
     private final HashMap<Integer, TranscodingJob> mPendingTranscodingJobs = new HashMap();
-    @NonNull private ITranscodingClient mTranscodingClient;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    @NonNull private ITranscodingClient mTranscodingClient = null;
+    private static MediaTranscodeManager sMediaTranscodeManager;
 
     private void handleTranscodingFinished(int jobId, TranscodingResultParcel result) {
         synchronized (mPendingTranscodingJobs) {
@@ -209,7 +239,7 @@
         }
     }
 
-    private void handleTranscodingFailed(int jobId, int errorCodec) {
+    private void handleTranscodingFailed(int jobId, int errorCode) {
         synchronized (mPendingTranscodingJobs) {
             // Gets the job associated with the jobId and removes it from
             // mPendingTranscodingJobs.
@@ -254,6 +284,98 @@
         }
     }
 
+    private static IMediaTranscodingService getService(boolean retry) {
+        int retryCount = !retry ? 1 :  CONNECT_SERVICE_RETRY_COUNT;
+        Log.i(TAG, "get service with rety " + retryCount);
+        for (int count = 1;  count <= retryCount; count++) {
+            Log.d(TAG, "Trying to connect to service. Try count: " + count);
+            IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface(
+                    ServiceManager.getService(MEDIA_TRANSCODING_SERVICE));
+            if (service != null) {
+                return service;
+            }
+            try {
+                // Sleep a bit before retry.
+                Thread.sleep(INTERVAL_CONNECT_SERVICE_RETRY_MS);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+
+        throw new UnsupportedOperationException("Failed to connect to MediaTranscoding service");
+    }
+
+    /*
+     * Handle client binder died event.
+     * Upon receiving a binder died event of the client, we will do the following:
+     * 1) For the job that is running, notify the client that the job is failed with error code,
+     *    so client could choose to retry the job or not.
+     *    TODO(hkuang): Add a new error code to signal service died error.
+     * 2) For the jobs that is still pending or paused, we will resubmit the job internally once
+     *    we successfully reconnect to the service and register a new client.
+     * 3) When trying to connect to the service and register a new client. The service may need time
+     *    to reboot or never boot up again. So we will retry for a number of times. If we still
+     *    could not connect, we will notify client job failure for the pending and paused jobs.
+     */
+    private void onClientDied() {
+        synchronized (mLock) {
+            mTranscodingClient = null;
+        }
+
+        // Delegates the job notification and retry to the executor as it may take some time.
+        mExecutor.execute(() -> {
+            // List to track the jobs that we want to retry.
+            List<TranscodingJob> retryJobs = new ArrayList<TranscodingJob>();
+
+            // First notify the client of job failure for all the running jobs.
+            synchronized (mPendingTranscodingJobs) {
+                for (Map.Entry<Integer, TranscodingJob> entry :
+                        mPendingTranscodingJobs.entrySet()) {
+                    TranscodingJob job = entry.getValue();
+
+                    if (job.getStatus() == TranscodingJob.STATUS_RUNNING) {
+                        job.updateStatusAndResult(TranscodingJob.STATUS_FINISHED,
+                                TranscodingJob.RESULT_ERROR);
+
+                        // Remove the job from pending jobs.
+                        mPendingTranscodingJobs.remove(entry.getKey());
+
+                        if (job.mListener != null && job.mListenerExecutor != null) {
+                            Log.i(TAG, "Notify client job failed");
+                            job.mListenerExecutor.execute(
+                                    () -> job.mListener.onTranscodingFinished(job));
+                        }
+                    } else if (job.getStatus() == TranscodingJob.STATUS_PENDING
+                            || job.getStatus() == TranscodingJob.STATUS_PAUSED) {
+                        // Add the job to retryJobs to handle them later.
+                        retryJobs.add(job);
+                    }
+                }
+            }
+
+            // Try to register with the service once it boots up.
+            IMediaTranscodingService service = getService(true /*retry*/);
+            boolean haveTranscodingClient = false;
+            if (service != null) {
+                synchronized (mLock) {
+                    mTranscodingClient = registerClient(service);
+                    if (mTranscodingClient != null) {
+                        haveTranscodingClient = true;
+                    }
+                }
+            }
+
+            for (TranscodingJob job : retryJobs) {
+                // Notify the job failure if we fails to connect to the service or fail
+                // to retry the job.
+                if (!haveTranscodingClient || !job.retry()) {
+                    // TODO(hkuang): Return correct error code to the client.
+                    handleTranscodingFailed(job.getJobId(), 0 /*unused */);
+                }
+            }
+        });
+    }
+
     private void updateStatus(int jobId, int status) {
         synchronized (mPendingTranscodingJobs) {
             final TranscodingJob job = mPendingTranscodingJobs.get(jobId);
@@ -336,6 +458,30 @@
                 }
             };
 
+    private ITranscodingClient registerClient(IMediaTranscodingService service)
+            throws UnsupportedOperationException {
+        synchronized (mLock) {
+            try {
+                // Registers the client with MediaTranscoding service.
+                mTranscodingClient = service.registerClient(
+                        mTranscodingClientCallback,
+                        mPackageName,
+                        mPackageName,
+                        IMediaTranscodingService.USE_CALLING_UID,
+                        IMediaTranscodingService.USE_CALLING_PID);
+
+                if (mTranscodingClient != null) {
+                    mTranscodingClient.asBinder().linkToDeath(() -> onClientDied(), /* flags */ 0);
+                }
+                return mTranscodingClient;
+            } catch (RemoteException re) {
+                Log.e(TAG, "Failed to register new client due to exception " + re);
+                mTranscodingClient = null;
+            }
+        }
+        throw new UnsupportedOperationException("Failed to register new client");
+    }
+
     /* Private constructor. */
     private MediaTranscodeManager(@NonNull Context context,
             IMediaTranscodingService transcodingService) {
@@ -344,21 +490,17 @@
         mPackageName = mContext.getPackageName();
         mPid = Os.getuid();
         mUid = Os.getpid();
-
-        try {
-            // Registers the client with MediaTranscoding service.
-            mTranscodingClient = transcodingService.registerClient(
-                    mTranscodingClientCallback,
-                    mPackageName,
-                    mPackageName,
-                    IMediaTranscodingService.USE_CALLING_UID,
-                    IMediaTranscodingService.USE_CALLING_PID);
-        } catch (RemoteException re) {
-            Log.e(TAG, "Failed to register new client due to exception " + re);
-            throw new UnsupportedOperationException("Failed to register new client");
-        }
+        mTranscodingClient = registerClient(transcodingService);
     }
 
+    @Override
+    protected void finalize() {
+        try {
+            release();
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to release");
+        }
+    }
 
     public static final class TranscodingRequest {
         /** Uri of the source media file. */
@@ -807,6 +949,16 @@
         }
 
         /**
+         * Resubmit the transcoding job to the service.
+         *
+         * @return true if successfully resubmit the job to the service. False otherwise.
+         */
+        public synchronized boolean retry() {
+            // TODO(hkuang): Implement this.
+            return true;
+        }
+
+        /**
          * Cancels the transcoding job and notify the listener.
          * If the job happened to finish before being canceled this call is effectively a no-op and
          * will not update the result in that case.
@@ -879,9 +1031,15 @@
      */
     public static MediaTranscodeManager getInstance(@NonNull Context context) {
         // Acquires the MediaTranscoding service.
-        IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface(
-                ServiceManager.getService(MEDIA_TRANSCODING_SERVICE));
+        IMediaTranscodingService service = getService(false /*retry*/);
+        return getInstance(context, service);
+    }
 
+    /** Similar as above, but wait till the service is ready. */
+    @VisibleForTesting
+    public static MediaTranscodeManager getInstance(@NonNull Context context, boolean retry) {
+        // Acquires the MediaTranscoding service.
+        IMediaTranscodingService service = getService(retry);
         return getInstance(context, service);
     }
 
@@ -896,6 +1054,7 @@
                 sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext(),
                         transcodingService);
             }
+
             return sMediaTranscodeManager;
         }
     }
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index ee8f1b3..df5e85e 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -622,4 +623,8 @@
         Log.w(className, "See the documentation of " + opName + " for what to use instead with " +
                 "android.media.AudioAttributes to qualify your playback use case");
     }
+
+    protected String getCurrentOpPackageName() {
+        return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
+    }
 }
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 70bd160..549e793 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -25,7 +25,6 @@
 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;
@@ -36,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;
@@ -103,6 +103,8 @@
      * System only flag for a session that needs to have priority over all other
      * sessions. This flag ensures this session will receive media button events
      * regardless of the current ordering in the system.
+     * If there are two or more sessions with this flag, the last session that sets this flag
+     * will be the global priority session.
      *
      * @hide
      */
@@ -491,7 +493,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/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 55aac09..bd8d2e9 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -33,6 +33,7 @@
 #include <utils/threads.h>
 #include "jni.h"
 #include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/Log.h"
@@ -944,10 +945,12 @@
 }
 
 static void
-android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+                                       jstring opPackageName)
 {
     ALOGV("native_setup");
-    sp<MediaPlayer> mp = new MediaPlayer();
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+    sp<MediaPlayer> mp = new MediaPlayer(opPackageNameStr.c_str());
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1403,7 +1406,7 @@
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
-    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
+    {"native_setup",        "(Ljava/lang/Object;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5770c67..5db6729 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -2060,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")));
@@ -2069,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/tests/MediaTranscodingTest/Android.bp b/media/tests/MediaTranscodingTest/Android.bp
index fcf8f11..907c3c8 100644
--- a/media/tests/MediaTranscodingTest/Android.bp
+++ b/media/tests/MediaTranscodingTest/Android.bp
@@ -8,6 +8,7 @@
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
         "android-support-test",
         "testng"
     ],
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
index 8fe1088..009a41e 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
@@ -23,15 +23,23 @@
 import android.media.MediaTranscodeManager.TranscodingJob;
 import android.media.MediaTranscodeManager.TranscodingRequest;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
 import org.junit.Test;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -127,8 +135,9 @@
         super.setUp();
 
         mContext = getInstrumentation().getContext();
-        mMediaTranscodeManager = MediaTranscodeManager.getInstance(mContext);
+        mMediaTranscodeManager = MediaTranscodeManager.getInstance(mContext, true /*retry*/);
         assertNotNull(mMediaTranscodeManager);
+        androidx.test.InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
 
         // Setup source HEVC file uri.
         mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.VideoOnlyHEVC, "VideoOnlyHEVC.mp4");
@@ -148,8 +157,6 @@
 
     @Test
     public void testTranscodingFromHevcToAvc() throws Exception {
-        Log.d(TAG, "Starting: testMediaTranscodeManager");
-
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
         // Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
@@ -194,6 +201,7 @@
                 stats.mAveragePSNR >= PSNR_THRESHOLD);
     }
 
+
     @Test
     public void testCancelTranscoding() throws Exception {
         Log.d(TAG, "Starting: testMediaTranscodeManager");
@@ -300,5 +308,94 @@
         assertTrue("Failed to receive at least 10 progress updates",
                 progressUpdateCount.get() > 10);
     }
+
+    // [[ $(adb shell whoami) == "root" ]]
+    private boolean checkIfRoot() throws IOException {
+        try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation()
+                .executeShellCommand("whoami");
+             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
+                     new FileInputStream(result.getFileDescriptor())))) {
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                if (line.contains("root")) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private String executeShellCommand(String cmd) throws Exception {
+        return UiDevice.getInstance(
+                InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+    }
+
+    @Test
+    public void testHandleTranscoderServiceDied() throws Exception {
+        try {
+            if (!checkIfRoot()) {
+                throw new AssertionError("must be root to run this test; try adb root?");
+            } else {
+                Log.i(TAG, "Device is root");
+            }
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+        Semaphore jobStartedSemaphore = new Semaphore(0);
+
+        // Transcode a 15 seconds video, so that the transcoding is not finished when we kill the
+        // service.
+        Uri srcUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/longtest_15s.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_ERROR);
+                    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() {
+                    @Override
+                    public void onProgressUpdate(int newProgress) {
+                        if (newProgress > 0) {
+                            jobStartedSemaphore.release();
+                        }
+                    }
+                });
+
+        // Wait for progress update so the job is in running state.
+        jobStartedSemaphore.tryAcquire(TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Job is not running", job.getStatus() == TranscodingJob.STATUS_RUNNING);
+
+        // Kills the service and expects receiving failure of the job.
+        executeShellCommand("pkill -f media.transcoding");
+
+        Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode result.");
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
+    }
 }
 
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index f9d0d14..e1b3151 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -11617,6 +11617,16 @@
     field public int version;
   }
 
+  public final class FileChecksum implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getKind();
+    method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+    method @Nullable public String getSplitName();
+    method @NonNull public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.FileChecksum> CREATOR;
+  }
+
   public final class InstallSourceInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getInitiatingPackageName();
@@ -11901,8 +11911,8 @@
     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_OTHER_ERROR = 4; // 0x4
     field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
     field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
   }
@@ -11992,6 +12002,7 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
+    method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
     method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
@@ -12082,6 +12093,7 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field public static final int DONT_KILL_APP = 1; // 0x1
+    field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
     field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays";
@@ -12236,6 +12248,8 @@
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
+    field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
+    field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
@@ -12245,9 +12259,16 @@
     field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
     field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
     field public static final int SYNCHRONOUS = 2; // 0x2
+    field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
+    field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
+    field public static final int WHOLE_MD5 = 2; // 0x2
+    field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
+    field public static final int WHOLE_SHA1 = 4; // 0x4
+    field public static final int WHOLE_SHA256 = 8; // 0x8
+    field public static final int WHOLE_SHA512 = 16; // 0x10
   }
 
   public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
@@ -14250,6 +14271,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);
@@ -35282,9 +35307,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();
@@ -45137,7 +45164,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;
   }
@@ -45986,6 +46013,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);
@@ -46502,7 +46530,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();
@@ -46524,6 +46554,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();
@@ -46571,6 +46602,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";
@@ -53328,7 +53363,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();
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 617a83f0..8892a29 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -46,6 +46,13 @@
     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 {
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index dbf9e9f..7cce0f2 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -219,6 +219,7 @@
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+    field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
@@ -7298,6 +7299,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);
@@ -9682,7 +9684,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);
@@ -10099,10 +10102,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();
@@ -10134,7 +10135,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);
@@ -10172,10 +10172,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";
@@ -11037,6 +11033,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
@@ -11584,8 +11581,8 @@
   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(long);
-    method public default long getNetworkHandle();
+    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);
   }
@@ -11726,7 +11723,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(long);
+    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 516e70f..f0b20c1 100644
--- a/packages/CarSystemUI/proguard.flags
+++ b/packages/CarSystemUI/proguard.flags
@@ -1,6 +1,7 @@
 -keep class com.android.systemui.CarSystemUIFactory
 -keep class com.android.car.notification.headsup.animationhelper.**
 
--keep class com.android.systemui.CarGlobalRootComponent { *; }
+-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/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/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
new file mode 100644
index 0000000..6161ad9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.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.
+  -->
+<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/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
new file mode 100644
index 0000000..3582142
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.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.
+  -->
+<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/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
new file mode 100644
index 0000000..afa5b32
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.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.
+  -->
+<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/CarSystemUI/samples/sample1/rro/res/values/colors.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
new file mode 100644
index 0000000..c32d638
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?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">
+    <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
index 854ab7d..2ec90e9 100644
--- a/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
@@ -17,8 +17,8 @@
 
 <resources>
     <!-- Configure which system bars should be displayed. -->
-    <bool name="config_enableTopNavigationBar">true</bool>
-    <bool name="config_enableLeftNavigationBar">true</bool>
+    <bool name="config_enableTopNavigationBar">false</bool>
+    <bool name="config_enableLeftNavigationBar">false</bool>
     <bool name="config_enableRightNavigationBar">true</bool>
     <bool name="config_enableBottomNavigationBar">true</bool>
 
@@ -28,8 +28,8 @@
     <!--    STATUS_BAR_EXTRA = 2-->
     <!--    NAVIGATION_BAR_EXTRA = 3-->
     <integer name="config_topSystemBarType">0</integer>
-    <integer name="config_leftSystemBarType">2</integer>
-    <integer name="config_rightSystemBarType">3</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.
@@ -40,8 +40,19 @@
          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">1</integer>
+    <integer name="config_topSystemBarZOrder">0</integer>
     <integer name="config_leftSystemBarZOrder">0</integer>
-    <integer name="config_rightSystemBarZOrder">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/CarSystemUI/samples/sample1/rro/res/values/dimens.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
new file mode 100644
index 0000000..cdfed27
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?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
+  -->
+<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
index 7bcb8e1..20aa5f7 100644
--- a/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
@@ -16,10 +16,51 @@
   -->
 
 <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"/>
@@ -30,4 +71,6 @@
     <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
index fde0b1a..b17ad0f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
@@ -16,13 +16,9 @@
 
 package com.android.systemui;
 
-import com.android.systemui.dagger.DependencyBinder;
-import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.GlobalModule;
 import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.dagger.SystemServicesModule;
-import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
-import com.android.systemui.pip.phone.dagger.PipModule;
+import com.android.systemui.dagger.WMModule;
 
 import javax.inject.Singleton;
 
@@ -32,15 +28,9 @@
 @Singleton
 @Component(
         modules = {
-                CarComponentBinder.class,
-                DependencyProvider.class,
-                DependencyBinder.class,
-                PipModule.class,
-                OneHandedModule.class,
-                SystemServicesModule.class,
-                SystemUIModule.class,
-                CarSystemUIModule.class,
-                CarSystemUIBinder.class
+                GlobalModule.class,
+                CarSysUIComponentModule.class,
+                WMModule.class
         })
 public interface CarGlobalRootComponent extends GlobalRootComponent {
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
index d19f368..1a6fdfa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.pip.phone.dagger.PipModule;
 
 import dagger.Subcomponent;
 
@@ -25,7 +28,13 @@
  * Dagger Subcomponent for Core SysUI.
  */
 @SysUISingleton
-@Subcomponent(modules = {})
+@Subcomponent(modules = {
+        CarComponentBinder.class,
+        DependencyProvider.class,
+        PipModule.class,
+        SystemUIModule.class,
+        CarSystemUIModule.class,
+        CarSystemUIBinder.class})
 public interface CarSysUIComponent extends SysUIComponent {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
copy to packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
index 114c30e..4de3166 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.systemui;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import dagger.Module;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface PipMenuActivityClass {
+/**
+ * Dagger module for including the CarSysUIComponent.
+ *
+ * TODO(b/162923491): Remove or otherwise refactor this module. This is a stop gap.
+ */
+@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/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 51ad286..3eea513 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -22,7 +22,6 @@
 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;
@@ -33,14 +32,13 @@
 import com.android.systemui.car.statusbar.DozeServiceHost;
 import com.android.systemui.car.volume.CarVolumeDialogComponent;
 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;
-import com.android.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -49,7 +47,6 @@
 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;
@@ -67,14 +64,9 @@
 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;
@@ -82,22 +74,19 @@
 
 @Module(
         includes = {
-                DividerModule.class,
-                QSModule.class
-        },
-        subcomponents = {
-                CarSysUIComponent.class
+                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,
@@ -109,7 +98,7 @@
                 groupManager, configurationController);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     static String provideLeakReportEmail() {
@@ -117,48 +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();
-    }
-
-    @Singleton
-    @PipMenuActivityClass
-    @Provides
-    static Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
-
     @Binds
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
@@ -170,7 +123,7 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static BatteryController provideBatteryController(Context context,
             EnhancedEstimates enhancedEstimates, PowerManager powerManager,
             BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
@@ -183,7 +136,7 @@
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
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..b8bf825 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,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.car.notification.BottomNotificationPanelViewMediator;
+import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.lang.annotation.ElementType;
@@ -40,13 +43,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();
@@ -95,6 +97,7 @@
         populateMaps();
         readConfigs();
         checkEnabledBarsHaveUniqueBarTypes();
+        checkSystemBarEnabledForNotificationPanel();
         setInsetPaddingsForOverlappingCorners();
         sortSystemBarSidesByZOrder();
     }
@@ -221,6 +224,34 @@
         }
     }
 
+    private void checkSystemBarEnabledForNotificationPanel() throws RuntimeException {
+
+        String notificationPanelMediatorName =
+                mResources.getString(R.string.config_notificationPanelViewMediator);
+        if (notificationPanelMediatorName == null) {
+            return;
+        }
+
+        Class<?> notificationPanelMediatorUsed = null;
+        try {
+            notificationPanelMediatorUsed = Class.forName(notificationPanelMediatorName);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        if (!mTopNavBarEnabled && notificationPanelMediatorUsed.isAssignableFrom(
+                TopNotificationPanelViewMediator.class)) {
+            throw new RuntimeException(
+                    "Top System Bar must be enabled to use " + notificationPanelMediatorName);
+        }
+
+        if (!mBottomNavBarEnabled && notificationPanelMediatorUsed.isAssignableFrom(
+                BottomNotificationPanelViewMediator.class)) {
+            throw new RuntimeException("Bottom System Bar must be enabled to use "
+                    + notificationPanelMediatorName);
+        }
+    }
+
     private void setInsetPaddingsForOverlappingCorners() {
         setInsetPaddingForOverlappingCorner(TOP, LEFT);
         setInsetPaddingForOverlappingCorner(TOP, RIGHT);
@@ -277,7 +308,7 @@
     }
 
     private static boolean isHorizontalBar(@SystemBarSide int side) {
-        return  side == TOP || side == BOTTOM;
+        return side == TOP || side == BOTTOM;
     }
 
     private static boolean isVerticalBar(@SystemBarSide int side) {
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..38e1a48 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 {
 
@@ -299,10 +299,10 @@
         // The glass pane is used to view touch events before passed to the notification list.
         // This allows us to initialize gesture listeners and detect when to close the notifications
         glassPane.setOnTouchListener((v, event) -> {
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            if (isOpeningAction(event)) {
                 mFirstTouchDownOnGlassPane = event.getRawX();
                 mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
                 // Reset the tracker when there is a touch down on the glass pane.
@@ -355,8 +355,7 @@
             if (rect != null) {
                 clippedHeight = rect.bottom;
             }
-            if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
-                    && mIsSwipingVerticallyToClose) {
+            if (!handled && isClosingAction(event) && mIsSwipingVerticallyToClose) {
                 if (getSettleClosePercentage() < getPercentageFromEndingEdge() && isTracking) {
                     animatePanel(DEFAULT_FLING_VELOCITY, false);
                 } else if (clippedHeight != getLayout().getHeight() && isTracking) {
@@ -369,7 +368,7 @@
             // Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after
             // the event has been passed to the closeGestureDetector above, such that the
             // closeGestureDetector sees the up event before the state has changed.
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
             return handled || isTracking;
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
index 0c064bd..1a1da89 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
@@ -17,13 +17,13 @@
 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;
-import javax.inject.Singleton;
 
 /** The automotive version of the notification shade window controller. */
-@Singleton
+@SysUISingleton
 public class NotificationShadeWindowControllerImpl implements
         NotificationShadeWindowController {
 
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 a2cd044..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 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/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/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 5bd8797..023b5b4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -131,7 +131,7 @@
     }
 
     private List<UserInfo> getUsersForUserGrid() {
-        return mUserManager.getUsers(/* excludeDying= */ true)
+        return mUserManager.getAliveUsers()
                 .stream()
                 .filter(UserInfo::supportsSwitchToByUser)
                 .collect(Collectors.toList());
@@ -338,7 +338,7 @@
                 maxSupportedUsers -= 1;
             }
 
-            List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */ true);
+            List<UserInfo> users = mUserManager.getAliveUsers();
 
             // Count all users that are managed profiles of another user.
             int managedProfilesCount = 0;
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 d453957..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,6 +19,7 @@
 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;
@@ -26,12 +27,11 @@
 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;
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/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index 45808a8..bde31f1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -191,6 +191,38 @@
         }
     }
 
+    /** Checks if a {@link MotionEvent} is an action to open the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if opening action.
+     */
+    protected boolean isOpeningAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        return false;
+    }
+
+    /** Checks if a {@link MotionEvent} is an action to close the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if closing action.
+     */
+    protected boolean isClosingAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        return false;
+    }
+
     /* ***************************************************************************************** *
      * Panel Animation
      * ***************************************************************************************** */
@@ -243,8 +275,7 @@
      * Depending on certain conditions, determines whether to fully expand or collapse the panel.
      */
     protected void maybeCompleteAnimation(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_UP
-                && isPanelVisible()) {
+        if (isClosingAction(event) && isPanelVisible()) {
             if (mSettleClosePercentage < mPercentageFromEndingEdge) {
                 animatePanel(DEFAULT_FLING_VELOCITY, false);
             } else {
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/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..6d31a8d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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);
+    }
+}
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/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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f42bf19..11d1b0a 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -483,6 +483,13 @@
     }
 
     @Override
+    protected void onDocIdDeleted(String docId) {
+        Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, docId);
+        getContext().revokeUriPermission(uri, ~0);
+    }
+
+
+    @Override
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
         synchronized (mRootsLock) {
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-fa/strings.xml b/packages/PrintSpooler/res/values-fa/strings.xml
index 596947d..719fc92 100644
--- a/packages/PrintSpooler/res/values-fa/strings.xml
+++ b/packages/PrintSpooler/res/values-fa/strings.xml
@@ -31,7 +31,7 @@
     <string name="template_all_pages" msgid="3322235982020148762">"همه <xliff:g id="PAGE_COUNT">%1$s</xliff:g> صفحه"</string>
     <string name="template_page_range" msgid="428638530038286328">"محدوده <xliff:g id="PAGE_COUNT">%1$s</xliff:g> صفحه"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"‏‏‎مثلاً ۱—۵،‏۹،۷—۱۰"</string>
-    <string name="print_preview" msgid="8010217796057763343">"پیش‌نمایش چاپ"</string>
+    <string name="print_preview" msgid="8010217796057763343">"پیش‌نمای چاپ"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"‏نصب نمایشگر PDF برای پیش‌نمایش"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"برنامه چاپ خراب شد"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"در حال ایجاد کار چاپ"</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/PrintSpooler/res/values-uz/strings.xml b/packages/PrintSpooler/res/values-uz/strings.xml
index d17bce7..ea0a6ea 100644
--- a/packages/PrintSpooler/res/values-uz/strings.xml
+++ b/packages/PrintSpooler/res/values-uz/strings.xml
@@ -100,7 +100,7 @@
   </string-array>
   <string-array name="orientation_labels">
     <item msgid="4061931020926489228">"Tik holat"</item>
-    <item msgid="3199660090246166812">"Eniga"</item>
+    <item msgid="3199660090246166812">"Yotiq"</item>
   </string-array>
     <string name="print_write_error_message" msgid="5787642615179572543">"Faylga yozib bo‘lmadi"</string>
     <string name="print_error_default_message" msgid="8602678405502922346">"Xatolik yuz berdi. Qaytadan urining."</string>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml
new file mode 100644
index 0000000..16e9190
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="25.50"
+    android:viewportHeight="25.50">
+    <group
+        android:translateX="0.77"
+        android:translateY="0.23" >
+        <path
+            android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+            android:fillColor="#FFFFFF"/>
+        <path
+            android:pathData="M22.71,15.67l-1.83,1.83l1.83,1.83c0.38,0.38 0.38,1 0,1.38v0c-0.38,0.38 -1,0.39 -1.38,0l-1.83,-1.83l-1.83,1.83c-0.38,0.38 -1,0.38 -1.38,0l-0.01,-0.01c-0.38,-0.38 -0.38,-1 0,-1.38l1.83,-1.83l-1.82,-1.82c-0.38,-0.38 -0.38,-1 0,-1.38l0.01,-0.01c0.38,-0.38 1,-0.38 1.38,0l1.82,1.82l1.82,-1.82c0.38,-0.38 1,-0.38 1.38,0l0,0C23.09,14.67 23.09,15.29 22.71,15.67z"
+            android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml
new file mode 100644
index 0000000..4c338c9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml
@@ -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.
+-->
+<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="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2.01C7.25,2.01 2.97,4.09 0,7.4L7.582,16.625C7.582,16.627 7.58,16.629 7.58,16.631L11.99,22L12,22L13,20.789L13,17.641L13,13.119C12.68,13.039 12.34,13 12,13C10.601,13 9.351,13.64 8.531,14.639L2.699,7.539C5.269,5.279 8.58,4.01 12,4.01C15.42,4.01 18.731,5.279 21.301,7.539L16.811,13L19.4,13L24,7.4C21.03,4.09 16.75,2.01 12,2.01z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml
new file mode 100644
index 0000000..79037db
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml
@@ -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.
+-->
+<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="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.081 0,7.391L12,22L13,20.779L13,17.631L13,13L16.801,13L18,13L19.391,13L24,7.391C21.03,4.081 16.75,2 12,2zM12,4C14.747,4 17.423,4.819 19.701,6.313C20.259,6.678 20.795,7.085 21.301,7.529L17.389,12.287C16.029,10.868 14.119,9.99 12,9.99C9.88,9.99 7.969,10.869 6.609,12.289L2.699,7.529C5.269,5.269 8.58,4 12,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml
new file mode 100644
index 0000000..21ad128
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml
@@ -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.
+-->
+<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="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.081 0,7.391L3.301,11.41L12,22L13,20.779L13,17.631L13,13L16.801,13L19.391,13L20.699,11.41C20.699,11.409 20.698,11.409 20.697,11.408L24,7.391C21.03,4.081 16.75,2 12,2zM12,4C15.42,4 18.731,5.269 21.301,7.529L19.35,9.9C17.43,8.1 14.86,6.99 12,6.99C9.14,6.99 6.57,8.1 4.65,9.9C4.65,9.901 4.649,9.902 4.648,9.902L2.699,7.529C5.269,5.269 8.58,4 12,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml
new file mode 100644
index 0000000..2ec5ba3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml
@@ -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.
+-->
+<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="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.08 0,7.39L12,22l1,-1.22V13h6.39L24,7.39C21.03,4.08 16.75,2 12,2z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index f3b22d3..5fc311a 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -139,7 +139,7 @@
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏قدرت سیگنال Wi‑Fi کامل است."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبکه باز"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبکه ایمن"</string>
-    <string name="process_kernel_label" msgid="950292573930336765">"‏سیستم عامل Android"</string>
+    <string name="process_kernel_label" msgid="950292573930336765">"‏سیستم‌عامل Android"</string>
     <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"برنامه‌های حذف شده"</string>
     <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"برنامه‌ها و کاربران حذف شده"</string>
     <string name="data_usage_ota" msgid="7984667793701597001">"به‌روزرسانی‌های سیستم"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index f219d24..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>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 83b72e9..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>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a43412e..b280806 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -49,11 +49,19 @@
     private static String sSharedSystemSharedLibPackageName;
 
     static final int[] WIFI_PIE = {
-            com.android.internal.R.drawable.ic_wifi_signal_0,
-            com.android.internal.R.drawable.ic_wifi_signal_1,
-            com.android.internal.R.drawable.ic_wifi_signal_2,
-            com.android.internal.R.drawable.ic_wifi_signal_3,
-            com.android.internal.R.drawable.ic_wifi_signal_4
+        com.android.internal.R.drawable.ic_wifi_signal_0,
+        com.android.internal.R.drawable.ic_wifi_signal_1,
+        com.android.internal.R.drawable.ic_wifi_signal_2,
+        com.android.internal.R.drawable.ic_wifi_signal_3,
+        com.android.internal.R.drawable.ic_wifi_signal_4
+    };
+
+    static final int[] SHOW_X_WIFI_PIE = {
+        R.drawable.ic_show_x_wifi_signal_0,
+        R.drawable.ic_show_x_wifi_signal_1,
+        R.drawable.ic_show_x_wifi_signal_2,
+        R.drawable.ic_show_x_wifi_signal_3,
+        R.drawable.ic_show_x_wifi_signal_4
     };
 
     public static void updateLocationEnabled(Context context, boolean enabled, int userId,
@@ -353,10 +361,22 @@
      * @throws IllegalArgumentException if an invalid RSSI level is given.
      */
     public static int getWifiIconResource(int level) {
+        return getWifiIconResource(false /* showX */, level);
+    }
+
+    /**
+     * Returns the Wifi icon resource for a given RSSI level.
+     *
+     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
+     *              signal icon to users.
+     * @param level The number of bars to show (0-4)
+     * @throws IllegalArgumentException if an invalid RSSI level is given.
+     */
+    public static int getWifiIconResource(boolean showX, int level) {
         if (level < 0 || level >= WIFI_PIE.length) {
             throw new IllegalArgumentException("No Wifi icon found for level: " + level);
         }
-        return WIFI_PIE[level];
+        return showX ? SHOW_X_WIFI_PIE[level] : WIFI_PIE[level];
     }
 
     public static int getDefaultStorageManagerDaysToRetain(Resources resources) {
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/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index a53bc9f..bba69f2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -35,6 +35,7 @@
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 import com.android.wifitrackerlib.WifiEntry;
+import com.android.wifitrackerlib.WifiEntry.ConnectedInfo;
 
 /**
  * Preference to display a WifiEntry in a wifi picker.
@@ -64,6 +65,7 @@
     private final IconInjector mIconInjector;
     private WifiEntry mWifiEntry;
     private int mLevel = -1;
+    private boolean mShowX; // Shows the Wi-Fi signl icon of Pie+x when it's true.
     private CharSequence mContentDescription;
     private OnButtonClickListener mOnButtonClickListener;
 
@@ -136,9 +138,15 @@
     public void refresh() {
         setTitle(mWifiEntry.getTitle());
         final int level = mWifiEntry.getLevel();
-        if (level != mLevel) {
+        final ConnectedInfo connectedInfo = mWifiEntry.getConnectedInfo();
+        boolean showX = false;
+        if (connectedInfo != null) {
+            showX = !connectedInfo.isDefaultNetwork || !connectedInfo.isValidated;
+        }
+        if (level != mLevel || showX != mShowX) {
             mLevel = level;
-            updateIcon(mLevel);
+            mShowX = showX;
+            updateIcon(mShowX, mLevel);
             notifyChanged();
         }
 
@@ -184,13 +192,13 @@
     }
 
 
-    private void updateIcon(int level) {
+    private void updateIcon(boolean showX, int level) {
         if (level == -1) {
             setIcon(null);
             return;
         }
 
-        final Drawable drawable = mIconInjector.getIcon(level);
+        final Drawable drawable = mIconInjector.getIcon(showX, level);
         if (drawable != null) {
             drawable.setTintList(Utils.getColorAttr(getContext(),
                     android.R.attr.colorControlNormal));
@@ -260,8 +268,8 @@
             mContext = context;
         }
 
-        public Drawable getIcon(int level) {
-            return mContext.getDrawable(Utils.getWifiIconResource(level));
+        public Drawable getIcon(boolean showX, int level) {
+            return mContext.getDrawable(Utils.getWifiIconResource(showX, level));
         }
     }
 
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/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/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index 46e699d..40af7dc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -29,6 +30,7 @@
 
 import com.android.settingslib.R;
 import com.android.wifitrackerlib.WifiEntry;
+import com.android.wifitrackerlib.WifiEntry.ConnectedInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,6 +64,17 @@
     @Mock
     private Drawable mMockDrawable4;
 
+    @Mock
+    private Drawable mMockShowXDrawable0;
+    @Mock
+    private Drawable mMockShowXDrawable1;
+    @Mock
+    private Drawable mMockShowXDrawable2;
+    @Mock
+    private Drawable mMockShowXDrawable3;
+    @Mock
+    private Drawable mMockShowXDrawable4;
+
     private static final String MOCK_TITLE = "title";
     private static final String MOCK_SUMMARY = "summary";
     private static final String FAKE_URI_STRING = "fakeuri";
@@ -75,11 +88,22 @@
         when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE);
         when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY);
 
-        when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0);
-        when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1);
-        when(mMockIconInjector.getIcon(2)).thenReturn(mMockDrawable2);
-        when(mMockIconInjector.getIcon(3)).thenReturn(mMockDrawable3);
-        when(mMockIconInjector.getIcon(4)).thenReturn(mMockDrawable4);
+        when(mMockIconInjector.getIcon(false /* showX */, 0)).thenReturn(mMockDrawable0);
+        when(mMockIconInjector.getIcon(false /* showX */, 1)).thenReturn(mMockDrawable1);
+        when(mMockIconInjector.getIcon(false /* showX */, 2)).thenReturn(mMockDrawable2);
+        when(mMockIconInjector.getIcon(false /* showX */, 3)).thenReturn(mMockDrawable3);
+        when(mMockIconInjector.getIcon(false /* showX */, 4)).thenReturn(mMockDrawable4);
+
+        when(mMockIconInjector.getIcon(true /* showX */, 0))
+                .thenReturn(mMockShowXDrawable0);
+        when(mMockIconInjector.getIcon(true /* showX */, 1))
+                .thenReturn(mMockShowXDrawable1);
+        when(mMockIconInjector.getIcon(true /* showX */, 2))
+                .thenReturn(mMockShowXDrawable2);
+        when(mMockIconInjector.getIcon(true /* showX */, 3))
+                .thenReturn(mMockShowXDrawable3);
+        when(mMockIconInjector.getIcon(true /* showX */, 4))
+                .thenReturn(mMockShowXDrawable4);
     }
 
     @Test
@@ -155,6 +179,70 @@
     }
 
     @Test
+    public void levelChanged_notDefaultWifiRefresh_shouldUpdateLevelIcon() {
+        final List<Drawable> iconList = new ArrayList<>();
+        final ConnectedInfo mockConnectedInfo = mock(ConnectedInfo.class);
+        mockConnectedInfo.isDefaultNetwork = false;
+        when(mMockWifiEntry.getConnectedInfo()).thenReturn(mockConnectedInfo);
+        final WifiEntryPreference pref =
+                new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
+
+        when(mMockWifiEntry.getLevel()).thenReturn(0);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(2);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(3);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(4);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(-1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+
+        assertThat(iconList).containsExactly(mMockShowXDrawable0, mMockShowXDrawable1,
+                mMockShowXDrawable2, mMockShowXDrawable3, mMockShowXDrawable4, null);
+    }
+
+    @Test
+    public void levelChanged_notValidatedWifiRefresh_shouldUpdateLevelIcon() {
+        final List<Drawable> iconList = new ArrayList<>();
+        final ConnectedInfo mockConnectedInfo = mock(ConnectedInfo.class);
+        mockConnectedInfo.isValidated = false;
+        when(mMockWifiEntry.getConnectedInfo()).thenReturn(mockConnectedInfo);
+        final WifiEntryPreference pref =
+                new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
+
+        when(mMockWifiEntry.getLevel()).thenReturn(0);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(2);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(3);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(4);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(-1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+
+        assertThat(iconList).containsExactly(mMockShowXDrawable0, mMockShowXDrawable1,
+                mMockShowXDrawable2, mMockShowXDrawable3, mMockShowXDrawable4, null);
+    }
+
+    @Test
     public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() {
         when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f1a3fee..bcd2ff7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -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/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 04916e2..3630f25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -262,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/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/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 807fbed..9c92b46 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2576,7 +2576,7 @@
         public void syncSsaidTableOnStart() {
             synchronized (mLock) {
                 // Verify that each user's packages and ssaid's are in sync.
-                for (UserInfo user : mUserManager.getUsers(true)) {
+                for (UserInfo user : mUserManager.getAliveUsers()) {
                     // Get all uids for the user's packages.
                     final List<PackageInfo> packages;
                     try {
@@ -3007,7 +3007,7 @@
 
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    List<UserInfo> users = mUserManager.getUsers(true);
+                    List<UserInfo> users = mUserManager.getAliveUsers();
 
                     final int userCount = users.size();
                     for (int i = 0; i < userCount; i++) {
@@ -3244,7 +3244,7 @@
             // is a singleton generation entry for the global settings which
             // is already incremented be the caller.
             final Uri uri = getNotificationUriFor(key, name);
-            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying*/ true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
                 if (mUserManager.isUserRunning(UserHandle.of(userId))) {
@@ -3255,7 +3255,7 @@
         }
 
         private void notifyLocationChangeForRunningUsers() {
-            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
 
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4bb8f45..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,
@@ -504,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/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/Android.bp b/packages/SystemUI/Android.bp
index 2fbd9ba..0f2e25c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -174,6 +174,9 @@
     kotlincflags: ["-Xjvm-default=enable"],
 
     dxflags: ["--multi-dex"],
-    required: ["privapp_whitelist_com.android.systemui"],
+    required: [
+        "privapp_whitelist_com.android.systemui",
+        "checked-wm_shell_protolog.json",
+    ],
 
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98d35532..7f4f580 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" />
@@ -526,20 +530,6 @@
             androidprv:alwaysFocusable="true"
             android:excludeFromRecents="true" />
 
-        <activity
-            android:name=".pip.phone.PipMenuActivity"
-            android:permission="com.android.systemui.permission.SELF"
-            android:theme="@style/PipPhoneOverlayControlTheme"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-            android:excludeFromRecents="true"
-            android:exported="false"
-            android:resizeableActivity="true"
-            android:supportsPictureInPicture="true"
-            android:stateNotNeeded="true"
-            android:taskAffinity=""
-            android:launchMode="singleTop"
-            androidprv:alwaysFocusable="true" />
-
         <!-- started from SliceProvider -->
         <activity android:name=".SlicePermissionActivity"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
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/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 9d52098..63f8b1f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -30,7 +30,7 @@
  */
 @ProvidesInterface(version = FalsingManager.VERSION)
 public interface FalsingManager {
-    int VERSION = 4;
+    int VERSION = 5;
 
     void onSuccessfulUnlock();
 
@@ -42,7 +42,8 @@
 
     boolean isUnlockingDisabled();
 
-    boolean isFalseTouch();
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseTouch(int interactionType);
 
     void onNotificatonStopDraggingDown();
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 02c4c5e..4b6efa9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -14,16 +14,16 @@
 
 package com.android.systemui.plugins.statusbar;
 
-import com.android.systemui.plugins.annotations.DependsOn;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
 @ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
 @DependsOn(target = SnoozeOption.class)
 public interface NotificationSwipeActionHelper {
@@ -52,7 +52,8 @@
 
     public boolean isDismissGesture(MotionEvent ev);
 
-    public boolean isFalseGesture(MotionEvent ev);
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseGesture();
 
     public boolean swipedFarEnough(float translation, float viewSize);
 
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 92e7d88..6c06b0a 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -41,6 +41,14 @@
     public <init>(android.content.Context);
 }
 
+# Keep the wm shell lib
 -keep class com.android.wm.shell.*
+# Keep the protolog group methods that are called by the generated code
+-keepclassmembers class com.android.wm.shell.protolog.ShellProtoLogGroup {
+    *;
+}
 
--keep class com.android.systemui.dagger.GlobalRootComponent { *; }
\ No newline at end of file
+-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/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/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/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/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/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index bac915d..316fa8a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Hervat"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Kanselleer"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Deel"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Vee uit"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Skermopname is gekanselleer"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Skermopname is gestoor, tik om te sien"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Skermopname is uitgevee"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Kon nie skermopname uitvee nie"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Kon nie toestemmings kry nie"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index aedbded..e2a4dc6 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>
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ከቆመበት ቀጥል"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ይቅር"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"አጋራ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ሰርዝ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"የማያ ገጽ ቀረጻ ተሰርዟል"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"የማያ ገጽ ቀረጻ ተቀምጧል፣ ለመመልከት መታ ያድርጉ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"የማያ ገጽ ቀረጻ ተሰርዟል"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"የማያ ገጽ ቀረጻን መሰረዝ ላይ ስህተት"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ፈቃዶችን ማግኘት አልተቻለም"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገጽ ቀረጻን መጀመር ላይ ስህተት"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index fee86ca..b33c6c5 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"استئناف"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"إلغاء"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"مشاركة"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"حذف"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"تمّ إلغاء تسجيل الشاشة."</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"تمّ حفظ تسجيل الشاشة، انقر لعرضه."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"تمّ حذف تسجيل الشاشة."</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"حدث خطأ أثناء حذف تسجيل الشاشة."</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"تعذّر الحصول على أذونات."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
@@ -1093,8 +1091,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 3778df8..5a3c659 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ৰখোৱাৰ পৰা পুনৰ আৰম্ভ কৰক"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"বাতিল কৰক"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"শ্বেয়াৰ কৰক"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"মচক"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"স্ক্রীণ ৰেকৰ্ড কৰাটো বাতিল কৰা হ’ল"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"স্ক্রীণ ৰেকৰ্ডিং ছেভ কৰা হ’ল, চাবলৈ টিপক"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"স্ক্রীণ ৰেকৰ্ডিং মচা হ’ল"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"স্ক্রীণ ৰেকৰ্ডিং মচি থাকোঁতে কিবা আসোঁৱাহ হ’ল"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"অনুমতি পাব পৰা নগ\'ল"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 874bf74..4aa4bda 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Davam edin"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Ləğv edin"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Paylaşın"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Silin"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekranın video çəkimi ləğv edildi"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekranın video çəkimi yadda saxlanıldı. Baxmaq üçün klikləyin"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekranın video çəkimi silindi"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Ekranın video çəkiminin silinməsi zamanı xəta baş verdi"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"İcazələr əldə edilmədi"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2a120c5..a9725f3 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Otkaži"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Deli"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Izbriši"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Snimanje ekrana je otkazano"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Snimak ekrana je sačuvan, dodirnite da biste pregledali"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Snimak ekrana je izbrisan"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Došlo je do problema pri brisanju snimka ekrana"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Preuzimanje dozvola nije uspelo"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index e4bb67a..b4c5eba 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Узнавіць"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Скасаваць"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Абагуліць"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Выдаліць"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Запіс экрана скасаваны"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Запіс экрана захаваны. Націсніце, каб прагледзець"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Запіс экрана выдалены"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Памылка выдалення запісу экрана"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Не ўдалося атрымаць дазволы"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c51e58c..52e8286 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Възобновяване"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Отказ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Споделяне"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Изтриване"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Записването на екрана е анулирано"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Записът на екрана е запазен. Докоснете, за да го видите"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Записът на екрана е изтрит"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"При изтриването на записа на екрана възникна грешка"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Извличането на разрешенията не бе успешно."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 3d00ca8..d0de9cf 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"আবার চালু করুন"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"বাতিল করুন"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"শেয়ার করুন"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"মুছুন"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"স্ক্রিন রেকর্ডিং বাতিল করা হয়েছে"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"স্ক্রিন রেকর্ডিং সেভ করা হয়েছে, দেখতে ট্যাপ করুন"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"স্ক্রিন রেকর্ডিং মুছে ফেলা হয়েছে"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"স্ক্রিন রেকডিং মুছে ফেলার সময় সমস্যা হয়েছে"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"অনুমতি পাওয়া যায়নি"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 1425984..57b7945 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Otkaži"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Dijeli"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Izbriši"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Snimanje ekrana je otkazano"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Snimak ekrana je sačuvan. Dodirnite za prikaz."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Snimak ekrana je izbrisan"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Greška prilikom brisanja snimka ekrana"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Dobijanje odobrenja nije uspjelo"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b150155..0489d18 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprèn"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancel·la"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Comparteix"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Suprimeix"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"S\'ha cancel·lat la gravació de la pantalla"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"S\'ha desat la gravació de la pantalla; toca per mostrar"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"S\'ha suprimit la gravació de la pantalla"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"S\'ha produït un error en suprimir la gravació de la pantalla"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"No s\'han pogut obtenir els permisos"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index ae6cc13..121a81d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Obnovit"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Zrušit"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Sdílet"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Smazat"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Nahrávání obrazovky bylo zrušeno"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Záznam obrazovky byl uložen, zobrazíte jej klepnutím"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Záznam obrazovky byl smazán"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Při mazání záznamu obrazovky došlo k chybě"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nepodařilo se načíst oprávnění"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 09f7f1a..8863695 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Genoptag"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Annuller"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Del"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Slet"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Skærmoptagelsen er annulleret"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Skærmoptagelsen er gemt. Tryk for at se den."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Skærmoptagelsen er slettet"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Der opstod en fejl ved sletning af skærmoptagelsen"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Det lykkedes ikke et hente tilladelserne"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 2410e47..8d990c6 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Fortsetzen"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Abbrechen"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Teilen"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Löschen"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Bildschirmaufzeichnung abgebrochen"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Bildschirmaufzeichnung gespeichert, zum Ansehen tippen"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Bildschirmaufzeichnung gelöscht"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Fehler beim Löschen der Bildschirmaufzeichnung"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Berechtigungen nicht erhalten"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index f66c5a8..4384749 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Συνέχιση"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Ακύρωση"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Κοινοποίηση"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Διαγραφή"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Η εγγραφή οθόνης ακυρώθηκε"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Η εγγραφή οθόνης αποθηκεύτηκε. Πατήστε για προβολή."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Η εγγραφή οθόνης διαγράφηκε"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Παρουσιάστηκε σφάλμα κατά τη διαγραφή της εγγραφής οθόνης"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Η λήψη αδειών απέτυχε"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 0e22b58..8a7963a 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancel"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Share"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Delete"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Screen recording cancelled"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Screen recording saved, tap to view"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Screen recording deleted"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error deleting screen recording"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Failed to get permissions"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index acf087d..7b2b25a 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancel"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Share"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Delete"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Screen recording cancelled"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Screen recording saved, tap to view"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Screen recording deleted"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error deleting screen recording"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Failed to get permissions"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 0e22b58..8a7963a 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancel"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Share"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Delete"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Screen recording cancelled"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Screen recording saved, tap to view"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Screen recording deleted"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error deleting screen recording"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Failed to get permissions"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 0e22b58..8a7963a 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancel"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Share"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Delete"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Screen recording cancelled"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Screen recording saved, tap to view"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Screen recording deleted"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error deleting screen recording"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Failed to get permissions"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 4f4238a..15738f2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‏‏‎Resume‎‏‎‎‏‎"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‎Cancel‎‏‎‎‏‎"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‎‎Share‎‏‎‎‏‎"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎Delete‎‏‎‎‏‎"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎Screen recording canceled‎‏‎‎‏‎"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎Screen recording saved, tap to view‎‏‎‎‏‎"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎Screen recording deleted‎‏‎‎‏‎"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‎Error deleting screen recording‎‏‎‎‏‎"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎Failed to get permissions‎‏‎‎‏‎"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎Error starting screen recording‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 45e0724..aa81ab8 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reanudar"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Compartir"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Borrar"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Se canceló la grabación de pantalla"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Se guardó la grabación de pantalla; presiona para verla"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Se borró la grabación de pantalla"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error al borrar la grabación de pantalla"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Error al obtener permisos"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index f7ebbd0..8638570 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Seguir"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Compartir"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Eliminar"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Se ha cancelado la grabación de la pantalla"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Se ha guardado la grabación de la pantalla; toca para verla"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Se ha eliminado la grabación de la pantalla"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"No se ha podido eliminar la grabación de la pantalla"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"No se han podido obtener los permisos"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 1e5e49e..f5ffad2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Jätka"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Tühista"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Jaga"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Kustuta"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekraanikuva salvestamine on tühistatud"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekraanikuva salvestis on salvestatud, puudutage vaatamiseks"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekraanikuva salvestis on kustutatud"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Viga ekraanikuva salvestise kustutamisel"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Lubade hankimine ebaõnnestus"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 38b2103d..1d5ac1a 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Berrekin"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Utzi"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Partekatu"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Ezabatu"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Utzi zaio pantaila grabatzeari"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Gorde da pantailaren grabaketa; sakatu ikusteko"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ezabatu da pantailaren grabaketa"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Errore bat gertatu da pantailaren grabaketa ezabatzean"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Ezin izan dira lortu baimenak"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index d39d0c3..55d8ab2 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ازسرگیری"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"لغو"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"هم‌رسانی"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"حذف"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ضبط صفحه‌نمایش لغو شد"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ضبط صفحه‌نمایش ذخیره شد، برای مشاهده ضربه بزنید"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"فایل ضبط صفحه‌نمایش حذف شد"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"خطا در حذف فایل ضبط صفحه‌نمایش"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"مجوزها دریافت نشدند"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحه‌نمایش"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index fa076c4..f30c44b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Jatka"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Peruuta"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Jaa"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Poista"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Näytön tallennus peruutettu"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Näyttötallenne tallennettu, katso napauttamalla"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Näyttötallenne poistettu"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Virhe poistettaessa näyttötallennetta"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Käyttöoikeuksien hakeminen epäonnistui."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 9c418f7..f6196dd63 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprendre"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Annuler"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Partager"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Supprimer"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"L\'enregistrement d\'écran a été annulé"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"L\'enregistrement d\'écran est terminé. Touchez ici pour l\'afficher."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"L\'enregistrement d\'écran a été supprimé"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Une erreur s\'est produite lors de la suppression de l\'enregistrement d\'écran"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Impossible d\'obtenir les autorisations"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3e2f5fd..0146ce4 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprendre"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Annuler"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Partager"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Supprimer"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Enregistrement de l\'écran annulé"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Enregistrement de l\'écran enregistré. Appuyez pour afficher"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Enregistrement de l\'écran supprimé"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Erreur lors de la suppression de l\'enregistrement de l\'écran"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Échec d\'obtention des autorisations"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9ecd75e..fd4d3c6 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Compartir"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Eliminar"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Cancelouse a gravación de pantalla"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Gardouse a gravación de pantalla; toca esta notificación para visualizala"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Eliminouse a gravación de pantalla"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Produciuse un erro ao eliminar a gravación de pantalla"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Produciuse un erro ao obter os permisos"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 6e05a54..46f7a2b 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ફરી શરૂ કરો"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"રદ કરો"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"શેર કરો"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ડિલીટ કરો"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"સ્ક્રીન રેકોર્ડિંગ રદ કર્યું"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"સ્ક્રીન રેકોર્ડિંગ સાચવ્યું, જોવા માટે ટૅપ કરો"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"સ્ક્રીન રેકોર્ડિંગ ડિલીટ કર્યું"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"સ્ક્રીન રેકોર્ડિંગ ડિલીટ કરવામાં ભૂલ આવી"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"પરવાનગીઓ મેળવવામાં નિષ્ફળ રહ્યાં"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
@@ -125,7 +123,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index de4fc48..e4a6ed7 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"फिर से शुरू करें"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"रद्द करें"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"शेयर करें"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"मिटाएं"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"स्क्रीन रिकॉर्डिंग रद्द कर दी गई"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"स्क्रीन रिकॉर्डिंग सेव की गई, देखने के लिए टैप करें"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"स्क्रीन रिकॉर्डिंग मिटा दी गई"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"स्क्रीन रिकॉर्डिंग मिटाने में गड़बड़ी हुई"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"मंज़ूरी नहीं मिल सकी"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4bebe79..a27b066 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Odustani"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Dijeli"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Izbriši"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Snimanje zaslona otkazano"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Snimanje zaslona spremljeno je, dodirnite da biste ga pregledali"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Snimanje zaslona izbrisano"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Pogreška prilikom brisanja snimanja zaslona"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Dohvaćanje dopuštenja nije uspjelo"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 42e251f..0d1f50a 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Folytatás"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Mégse"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Megosztás"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Törlés"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"A képernyő rögzítése megszakítva"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Képernyőfelvétel mentve, koppintson a megtekintéshez"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"A képernyőről készült felvétel törölve"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Hiba történt a képernyőről készült felvétel törlésekor"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nincs engedély"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index e6b7139..f9a10fa 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Վերսկսել"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Չեղարկել"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Կիսվել"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Ջնջել"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Էկրանի տեսագրումը չեղարկվեց"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Էկրանի տեսագրությունը պահվեց։ Հպեք՝ դիտելու համար:"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Էկրանի տեսագրությունը ջնջվեց"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Չհաջողվեց ջնջել տեսագրությունը"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Չհաջողվեց ստանալ անհրաժեշտ թույլտվությունները"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
@@ -337,7 +335,7 @@
     <string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"Էկրանը կողպված է ուղղաձիգ դիրքավորմամբ:"</string>
     <string name="accessibility_rotation_lock_off_changed" msgid="5772498370935088261">"Էկրանն այժմ ավտոմատ կպտտվի:"</string>
     <string name="accessibility_rotation_lock_on_landscape_changed" msgid="5785739044300729592">"Էկրանն այժմ կողպված է հորիզոնական դիրքում:"</string>
-    <string name="accessibility_rotation_lock_on_portrait_changed" msgid="5580170829728987989">"Էկրանն այժմ կողպված է ուղղահայաց դիրքում:"</string>
+    <string name="accessibility_rotation_lock_on_portrait_changed" msgid="5580170829728987989">"Էկրանն այժմ կողպված է ուղղաձիգ դիրքում:"</string>
     <string name="dessert_case" msgid="9104973640704357717">"Dessert Case"</string>
     <string name="start_dreams" msgid="9131802557946276718">"Էկրանապահ"</string>
     <string name="ethernet_label" msgid="2203544727007463351">"Ethernet"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index fec4205..f65be90 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Lanjutkan"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Batal"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Bagikan"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Hapus"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Rekaman layar dibatalkan"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Rekaman layar disimpan, ketuk untuk melihat"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Rekaman layar dihapus"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error saat menghapus rekaman layar"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Gagal mendapatkan izin"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 3a9e63b..66c9147 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Halda áfram"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Hætta við"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Deila"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Eyða"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Hætt við skjáupptöku"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Skjáupptaka vistuð, ýttu til að skoða"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Skjáupptöku eytt"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Villa við að eyða skjáupptöku"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Ekki tókst að fá heimildir"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 3eca501..b0e64a2 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Riprendi"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Annulla"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Condividi"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"CANC"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Registrazione dello schermo annullata"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Registrazione dello schermo salvata. Tocca per visualizzarla."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Registrazione dello schermo eliminata"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Errore durante l\'eliminazione della registrazione dello schermo"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Impossibile ottenere le autorizzazioni"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index e88c951..92fa09b 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"המשך"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ביטול"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"שיתוף"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"מחיקה"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"הקלטת המסך בוטלה"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"הקלטת המסך נשמרה, יש להקיש כדי להציג"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"הקלטת המסך נמחקה"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"שגיאה במחיקת הקלטת המסך"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"קבלת ההרשאות נכשלה"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 130f682..e427c8f 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"再開"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"キャンセル"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"共有"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"削除"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"画面の録画をキャンセルしました"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"画面の録画を保存しました。タップで表示できます"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"画面の録画を削除しました"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"画面の録画の削除中にエラーが発生しました"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"権限を取得できませんでした"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d050875..b86bf8c 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"გაგრძელება"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"გაუქმება"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"გაზიარება"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"წაშლა"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ეკრანის ჩაწერა გაუქმდა"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ეკრანის ჩანაწერი შენახულია, შეეხეთ სანახავად"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ეკრანის ჩანაწერი წაიშალა"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ეკრანის ჩანაწერის წაშლისას წარმოიშვა შეცდომა"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ნებართვების მიღება ვერ მოხერხდა"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 36c726e..1adaf2b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Жалғастыру"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Бас тарту"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Бөлісу"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Жою"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Экранды бейнеге жазудан бас тартылды"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Экран бейне жазбасы сақталды, көру үшін түртіңіз"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Экран бейне жазбасы жойылды"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Экран бейне жазбасын жою кезінде қате кетті"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Рұқсаттар алынбады"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9d87c58..961a37b 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"បន្ត"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"បោះបង់"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ចែករំលែក"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"លុប"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"បាន​បោះបង់​ការថត​សកម្មភាព​អេក្រង់"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"បានរក្សាទុក​ការថត​សកម្មភាព​អេក្រង់។ សូមចុច​ដើម្បី​មើល"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"បានលុប​ការថត​សកម្មភាព​អេក្រង់"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"មានបញ្ហា​ក្នុងការ​លុបការថត​សកម្មភាព​អេក្រង់"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"មិនអាច​ទទួលបាន​ការអនុញ្ញាត​ទេ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហា​ក្នុងការ​ចាប់ផ្ដើម​ថត​អេក្រង់"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 901f024..7ec6a9f 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ಮುಂದುವರಿಸಿ"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ರದ್ದುಮಾಡಿ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ಅಳಿಸಿ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಅನ್ನು ರದ್ದುಮಾಡಲಾಗಿದೆ"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ, ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಅಳಿಸಲಾಗಿದೆ"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಅಳಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ಅನುಮತಿಗಳನ್ನು ಪಡೆಯುವಲ್ಲಿ ವಿಫಲವಾಗಿದೆ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8a64155..840a98e 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"재개"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"취소"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"공유"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"삭제"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"화면 녹화가 취소되었습니다."</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"화면 녹화본이 저장되었습니다. 확인하려면 탭하세요."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"화면 녹화가 삭제되었습니다."</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"화면 녹화는 삭제하는 중에 오류가 발생했습니다."</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"권한을 확보하지 못했습니다."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
@@ -502,10 +500,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 3cf4126..14ee566 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Улантуу"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Жокко чыгаруу"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Бөлүшүү"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Ооба"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Экранды жаздыруу жокко чыгарылды"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Экранды жаздыруу сакталды, көрүү үчүн таптап коюңуз"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Экранды жаздыруу өчүрүлдү"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Экранды жаздырууну өчүрүүдө ката кетти"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Уруксаттар алынбай калды"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index b1c5a5c..553d0da 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ສືບຕໍ່"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ຍົກເລີກ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ແບ່ງປັນ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ລຶບ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ຍົກເລີກການບັນທຶກໜ້າຈໍແລ້ວ"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ຈັດເກັບການບັນທຶກໜ້າຈໍ, ແຕະເພື່ອເບິ່ງ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ລຶບການບັນທຶກໜ້າຈໍອອກແລ້ວ"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ເກີດຄວາມຜິດພາດໃນການລຶບການບັນທຶກໜ້າຈໍ"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ໂຫຼດສິດອະນຸຍາດບໍ່ສຳເລັດ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 4e9b802..28d6e6b 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Tęsti"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Atšaukti"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Bendrinti"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Ištrinti"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekrano įrašymas atšauktas"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekrano įrašas išsaugotas, palieskite ir peržiūrėkite"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekrano įrašas ištrintas"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Ištrinant ekrano įrašą įvyko klaida"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nepavyko gauti leidimų"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a4c4666..223a57f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Atsākt"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Atcelt"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Kopīgot"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Dzēst"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekrāna ierakstīšana ir atcelta."</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekrāna ieraksts ir saglabāts. Pieskarieties, lai to skatītu."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekrāna ieraksts ir izdzēsts."</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Dzēšot ekrāna ierakstu, radās kļūda."</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Neizdevās iegūt atļaujas."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 7e623ad..a591031 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Продолжи"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Откажи"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Сподели"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Избриши"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Снимањето екран е откажано"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Снимката од екранот е зачувана, допрете за да ја видите"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Снимката од екранот е избришана"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Грешка при бришењето на снимката од екранот"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Не успеаја да се добијат дозволи"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
@@ -667,7 +665,7 @@
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забава за некои, но не за сите"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Адаптерот на УИ на системот ви дава дополнителни начини за дотерување и приспособување на корисничкиот интерфејс на Android. Овие експериментални функции можеби ќе се изменат, расипат или ќе исчезнат во следните изданија. Продолжете со претпазливост."</string>
     <string name="tuner_persistent_warning" msgid="230466285569307806">"Овие експериментални функции можеби ќе се изменат, расипат или ќе исчезнат во следните изданија. Продолжете со претпазливост."</string>
-    <string name="got_it" msgid="477119182261892069">"Разбрав"</string>
+    <string name="got_it" msgid="477119182261892069">"Сфатив"</string>
     <string name="tuner_toast" msgid="3812684836514766951">"Честито! Го додадовте Адаптерот на УИ на системот на Поставки"</string>
     <string name="remove_from_settings" msgid="633775561782209994">"Отстрани од поставки"</string>
     <string name="remove_from_settings_prompt" msgid="551565437265615426">"Да се отстрани Адаптерот на УИ на системот од Поставки и да престанат да се користат сите негови функции?"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 35e6427..47d220d 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"പുനരാരംഭിക്കുക"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"റദ്ദാക്കുക"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"പങ്കിടുക"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ഇല്ലാതാക്കുക"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"സ്ക്രീൻ റെക്കോർഡിംഗ് റദ്ദാക്കി"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിച്ചു, കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"സ്ക്രീൻ റെക്കോർഡിംഗ് ഇല്ലാതാക്കി"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"സ്ക്രീൻ റെക്കോർഡിംഗ് ഇല്ലാതാക്കുന്നതിൽ പിശക്"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"അനുമതികൾ ലഭിച്ചില്ല"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b4690d4..202f292 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Үргэлжлүүлэх"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Цуцлах"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Хуваалцах"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Устгах"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Дэлгэцийн бичлэгийг цуцалсан"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Дэлгэцийн бичлэгийг хадгалсан. Харахын тулд товшино уу"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Дэлгэцийн бичлэгийг устгасан"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Дэлгэцийн бичлэгийг устгахад алдаа гарлаа"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Зөвшөөрөл авч чадсангүй"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 4ea965a..e412072 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"पुन्हा सुरू करा"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"रद्द करा"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"शेअर करा"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"हटवा"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"स्क्रीन रेकॉर्डिंग रद्द केले"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"स्क्रीन रेकॉर्डिंग सेव्ह केली, पाहण्यासाठी टॅप करा"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"स्क्रीन रेकॉर्डिंग हटवले"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"स्क्रीन रेकॉर्डिंग हटवताना एरर आली"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"परवानग्या मिळवता आल्या नाहीत"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 38ee25c..d01ab8c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Sambung semula"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Batal"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Kongsi"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Padam"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Rakaman skrin dibatalkan"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Rakaman skrin disimpan, ketik untuk melihat"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Rakaman skrin dipadamkan"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Ralat semasa memadamkan rakaman skrin"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Gagal mendapatkan kebenaran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 150ed94e..83272f2 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ဆက်လုပ်ရန်"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"မလုပ်တော့"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"မျှဝေရန်"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ဖျက်ရန်"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ဖန်သားပြင် ရိုက်ကူးမှု ပယ်ဖျက်လိုက်ပါပြီ"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ဖန်သားပြင် ရိုက်ကူးမှု သိမ်းထားသည်၊ ကြည့်ရန် တို့ပါ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ဖန်သားပြင် ရိုက်ကူးမှု ဖျက်ပြီးပါပြီ"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ဖန်သားပြင် ရိုက်ကူးမှု ဖျက်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ခွင့်ပြုချက် မရယူနိုင်ပါ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d872a89..01859ef 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Gjenoppta"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Avbryt"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Del"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Slett"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Skjermopptak er avbrutt"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Skjermopptaket er lagret. Trykk for å se det"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Skjermopptaket er slettet"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Feil ved sletting av skjermopptaket"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Kunne ikke få tillatelser"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 1974e80..e548a96 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"जारी राख्नुहोस्"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"रद्द गर्नुहोस्"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"सेयर गर्नुहोस्"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"मेट्नुहोस्"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"स्क्रिन रेकर्ड गर्ने कार्य रद्द गरियो"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"स्क्रिन रेकर्डिङ सुरक्षित गरियो, हेर्न ट्याप गर्नुहोस्‌"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"स्क्रिनको रेकर्डिङ मेटाइयो"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"स्क्रिनको रेकर्डिङ मेट्ने क्रममा त्रुटि"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"अनुमति प्राप्त गर्न सकिएन"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 9da8afa..fa028c3 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Hervatten"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Annuleren"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Delen"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Verwijderen"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Schermopname geannuleerd"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Schermopname opgeslagen, tik om te bekijken"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Schermopname verwijderd"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Fout bij verwijderen van schermopname"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Kan rechten niet ophalen"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
@@ -354,7 +352,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Gehoorapparaten"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hoortoestellen"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inschakelen..."</string>
     <string name="quick_settings_brightness_label" msgid="680259653088849563">"Helderheid"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 5b5cbc6..4c6ef48 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ବାତିଲ୍‌ କରନ୍ତୁ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ସେୟାର୍‍ କରନ୍ତୁ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ଡିଲିଟ୍ କରନ୍ତୁ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ସ୍କ୍ରିନ୍‍ ରେକର୍ଡିଂ ବାତିଲ୍‌ କରିଦିଆଯାଇଛି"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ସ୍କ୍ରିନ୍‍ ରେକର୍ଡିଂ ସେଭ୍‍ ହୋଇଛି, ଦେଖିବାକୁ ଟାପ୍‍ କରନ୍ତୁ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ସ୍କ୍ରିନ୍‍ ରେକର୍ଡିଂ ଡିଲିଟ୍‍ କରିଦିଆଯାଇଛି"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ସ୍କ୍ରିନ୍‍ ରେକର୍ଡିଂ ଡିଲିଟ୍‍ କରିବାରେ ତ୍ରୁଟି"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ଅନୁମତି ପାଇବାରେ ଅସଫଳ ହେଲା।"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 5c31ce7..98583c0 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ਰੱਦ ਕਰੋ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ਸਾਂਝਾ ਕਰੋ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ਮਿਟਾਓ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ਸਕ੍ਰੀਨ ਦੀ ਰਿਕਾਰਡਿੰਗ ਰੱਦ ਕੀਤੀ ਗਈ"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਰੱਖਿਅਤ ਕੀਤੀ ਗਈ, ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਮਿਟਾਇਆ ਗਿਆ"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਮਿਟਾਉਣ ਦੌਰਾਨ ਗੜਬੜ ਹੋਈ"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ਇਜਾਜ਼ਤਾਂ ਪ੍ਰਾਪਤ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index b7ee750..4d5a2ae 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Wznów"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Anuluj"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Udostępnij"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Usuń"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Anulowano nagrywanie zawartości ekranu"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Zapisano nagranie zawartości ekranu – kliknij, by je obejrzeć"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Usunięto nagranie zawartości ekranu"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Błąd podczas usuwania nagrania zawartości ekranu"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nie udało się uzyskać uprawnień"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 715b0e4..ef5c9ce 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Compartilhar"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Excluir"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Gravação de tela cancelada"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Gravação de tela salva, toque para ver"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Gravação de tela excluída"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Erro ao excluir a gravação de tela"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Não foi possível acessar as permissões"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 1a59de4..541f12c 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Partilhar"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Eliminar"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Gravação de ecrã cancelada."</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Gravação de ecrã guardada. Toque para ver."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Gravação de ecrã eliminada."</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Erro ao eliminar a gravação de ecrã."</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Falha ao obter as autorizações."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 715b0e4..ef5c9ce 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Cancelar"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Compartilhar"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Excluir"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Gravação de tela cancelada"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Gravação de tela salva, toque para ver"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Gravação de tela excluída"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Erro ao excluir a gravação de tela"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Não foi possível acessar as permissões"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 49c3ba0..3e2373d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reluați"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Anulați"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Trimiteți"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Ștergeți"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Înregistrarea ecranului a fost anulată"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Înregistrarea ecranului a fost salvată. Atingeți pentru vizualizare"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Înregistrarea ecranului a fost ștearsă."</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Eroare la ștergerea înregistrării ecranului"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nu s-au obținut permisiunile"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 7fb456d..7747724 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Возобновить"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Отмена"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Поделиться"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Удалить"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Запись видео с экрана отменена"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Запись видео с экрана сохранена. Чтобы открыть ее, нажмите на уведомление."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Запись видео с экрана удалена"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Не удалось удалить запись видео с экрана"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Не удалось получить необходимые разрешения"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 1cd1ceb..b8813f2 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"නැවත අරඹන්න"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"අවලංගු කරන්න"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"බෙදා ගන්න"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"මකන්න"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"තිර පටිගත කිරීම අවලංගු කරන ලදී"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"තිර පටිගත කිරීම සුරකින ලදී, බැලීමට තට්ටු කරන්න"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"තිර පටිගත කිරීම මකන ලදී"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"තිර පටිගත කිරීම මැකීමේ දෝෂයකි"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"අවසර ලබා ගැනීමට අසමත් විය"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 9d7e7d1..0f9cb5f 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Obnoviť"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Zrušiť"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Zdieľať"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Odstrániť"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Záznam obrazovky bol zrušený"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Záznam obrazovky bol uložený, zobrazíte ho klepnutím"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Záznam obrazovky bol odstránený"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Pri odstraňovaní záznamu obrazovky sa vyskytla chyba"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Nepodarilo sa získať povolenia"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ecbf1d9..18db332 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nadaljuj"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Prekliči"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Deli"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Izbriši"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Snemanje zaslona je preklicano"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Videoposnetek zaslona je shranjen, dotaknite se za ogled"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Videoposnetek zaslona je izbrisan"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Napaka pri brisanju videoposnetka zaslona"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Dovoljenj ni bilo mogoče pridobiti"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 70245b8..57ab1c3 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -108,15 +108,13 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Vazhdo"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Anulo"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Ndaj"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Fshi"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Regjistrimi i ekranit u anulua"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Regjistrimi i ekranit u ruajt, trokit për ta parë"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Regjistrimi i ekranit u fshi"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Gabim gjatë fshirjes së regjistrimit të ekranit"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Marrja e lejeve dështoi"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
     <string name="usb_preference_title" msgid="1439924437558480718">"Opsionet e transferimit të dosjeve të USB-së"</string>
-    <string name="use_mtp_button_title" msgid="5036082897886518086">"Lidh si një lexues \"media\" (MTP)"</string>
+    <string name="use_mtp_button_title" msgid="5036082897886518086">"Lidh si një luajtës të medias (MTP)"</string>
     <string name="use_ptp_button_title" msgid="7676427598943446826">"Montoje si kamerë (PTP)"</string>
     <string name="installer_cd_button_title" msgid="5499998592841984743">"Instalo \"Transferimi i skedarëve të Android\" për Mac"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1d02ca6..ec763f6 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Настави"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Откажи"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Дели"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Избриши"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Снимање екрана је отказано"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Снимак екрана је сачуван, додирните да бисте прегледали"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Снимак екрана је избрисан"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Дошло је до проблема при брисању снимка екрана"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Преузимање дозвола није успело"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
@@ -1075,8 +1073,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7a7448b..a0a563d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Återuppta"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Avbryt"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Dela"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Radera"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Skärminspelningen har avbrutits"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Skärminspelningen har sparats, tryck här om du vill titta på den"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Skärminspelningen har raderats"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Det gick inte att radera skärminspelningen"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Behörighet saknas"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b8d95fc..7e264a4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Endelea"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Ghairi"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Shiriki"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Futa"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Imeghairi mchakato wa kurekodi skrini"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Imehifadhi rekodi ya skrini, gusa ili uangalie"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Imefuta rekodi ya skrini"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Hitilafu imetokea wakati wa kufuta rekodi ya skrini"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Imeshindwa kupata ruhusa"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 60c1cf9..e0f20ce 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"மீண்டும் தொடங்கு"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ரத்துசெய்"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"பகிர்"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"நீக்கு"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"திரை ரெக்கார்டிங் ரத்துசெய்யப்பட்டது"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"திரை ரெக்கார்டிங் சேமிக்கப்பட்டது, பார்க்கத் தட்டவும்"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"திரை ரெக்கார்டிங் நீக்கப்பட்டது"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"திரை ரெக்கார்டிங்கை நீக்குவதில் பிழை"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"அனுமதிகளைப் பெற இயலவில்லை"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 6cfe03d..3e94b72 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"కొనసాగించు"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"రద్దు చేయి"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"షేర్ చేయి"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"తొలగించు"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"స్క్రీన్ రికార్డ్ రద్దు చేయబడింది"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"స్క్రీన్ రికార్డింగ్ సేవ్ చేయబడింది, చూడటం కోసం నొక్కండి"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"స్క్రీన్ రికార్డింగ్ తొలగించబడింది"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"స్క్రీన్ రికార్డింగ్‌ని తొలగిస్తున్నప్పుడు ఎర్రర్ ఏర్పడింది"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"అనుమతులను పొందడం విఫలమైంది"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
@@ -125,7 +123,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 +439,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,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 7b1479a..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,6 +41,7 @@
         <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 -->
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index a2c1127..088b624 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"ทำต่อ"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"ยกเลิก"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"แชร์"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"ลบ"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"ยกเลิกการบันทึกหน้าจอแล้ว"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"บันทึกการบันทึกหน้าจอแล้ว แตะเพื่อดู"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"ลบการบันทึกหน้าจอแล้ว"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"เกิดข้อผิดพลาดในการลบการบันทึกหน้าจอ"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"ขอสิทธิ์ไม่สำเร็จ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e5f0532..e787b17 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Ituloy"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Kanselahin"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Ibahagi"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"I-delete"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Kinansela ang pag-record ng screen"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Na-save ang pag-record ng screen, i-tap para tingnan"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Na-delete ang pag-record ng screen"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Error sa pag-delete sa pag-record ng screen"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Hindi nakuha ang mga pahintulot"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 4943e2d..e339445 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Devam ettir"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"İptal"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Paylaş"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Sil"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekran kaydı iptal edildi"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekran kaydı tamamlandı, görüntülemek için dokunun"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekran kaydı silindi"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Ekran kaydı silinirken hata oluştu"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"İzinler alınamadı"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 447912e..f5419f1 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Відновити"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Скасувати"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Поділитися"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Видалити"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Запис екрана скасовано"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Запис екрана збережено. Натисніть, щоб переглянути"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Запис екрана видалено"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Не вдалося видалити запис екрана"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Не вдалось отримати дозволи"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
@@ -1081,8 +1079,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 20b5518..2abf350 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"دوبارہ شروع کریں"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"منسوخ کریں"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"اشتراک کریں"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"حذف کریں"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"اسکرین ریکارڈنگ منسوخ ہو گئی"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"اسکرین ریکارڈنگ محفوظ ہو گئی، دیکھنے کیلئے تھپتھپائیں"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"اسکرین ریکارڈنگ حذف ہو گئی"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"اسکرین ریکارڈنگ کو حذف کرنے میں خرابی"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"اجازتیں حاصل کرنے میں ناکامی"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index cf1da75..61b10a4 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Davom etish"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Bekor qilish"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Ulashish"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"O‘chirish"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ekrandan yozib olish bekor qilindi"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ekrandan yozib olingan video saqlandi. Uni ochish uchun bildirishnomani bosing."</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ekrandan yozib olingan video o‘chirib tashlandi"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Ekrandan yozib olingan vi olib tashlanmadi"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Zarur ruxsatlar olinmadi"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
@@ -362,7 +360,7 @@
     <string name="accessibility_quick_settings_rotation_value" msgid="2916484894750819251">"<xliff:g id="ID_1">%s</xliff:g> rejimi"</string>
     <string name="quick_settings_rotation_locked_label" msgid="4420863550666310319">"Aylanmaydigan qilingan"</string>
     <string name="quick_settings_rotation_locked_portrait_label" msgid="1194988975270484482">"Tik holat"</string>
-    <string name="quick_settings_rotation_locked_landscape_label" msgid="2000295772687238645">"Eniga"</string>
+    <string name="quick_settings_rotation_locked_landscape_label" msgid="2000295772687238645">"Yotiq"</string>
     <string name="quick_settings_ime_label" msgid="3351174938144332051">"Kiritish usuli"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string>
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Joylashuvni aniqlash xizmati yoqilmagan"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index a12a08d..6107270 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Tiếp tục"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Hủy"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Chia sẻ"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Xóa"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Đã hủy bản ghi màn hình"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Đã lưu bản ghi màn hình, nhấn để xem"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Đã xóa bản ghi màn hình"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Lỗi khi xóa bản ghi màn hình"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Không được cấp đủ quyền"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 3382365..ffd1995 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"继续"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"取消"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"分享"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"删除"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"已取消录制屏幕"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"屏幕录制内容已保存,点按即可查看"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"已删除屏幕录制内容"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"删除屏幕录制内容时出错"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"无法获取权限"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 1d55ca2..1575427 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"繼續"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"取消"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"分享"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"刪除"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"已取消錄影畫面"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"已儲存錄影畫面,輕按即可查看"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"已刪除錄影畫面"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"刪除錄影畫面時發生錯誤"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"無法獲得權限"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index e62c164..bbd8355 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"繼續"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"取消"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"分享"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"刪除"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"已取消錄製螢幕畫面"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"已儲存螢幕畫面錄製內容,輕觸即可查看"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"已刪除螢幕畫面錄製內容"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"刪除螢幕畫面錄製內容時發生錯誤"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"無法取得權限"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index bd73912..63b4c61 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -108,10 +108,8 @@
     <string name="screenrecord_resume_label" msgid="4972223043729555575">"Qalisa kabusha"</string>
     <string name="screenrecord_cancel_label" msgid="7850926573274483294">"Khansela"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Yabelana"</string>
-    <string name="screenrecord_delete_label" msgid="1376347010553987058">"Susa"</string>
     <string name="screenrecord_cancel_success" msgid="1775448688137393901">"Ukurekhoda isikrini kukhanseliwe"</string>
     <string name="screenrecord_save_message" msgid="490522052388998226">"Ukurekhoda isikrini kulondoloziwe, thepha ukuze ubuke"</string>
-    <string name="screenrecord_delete_description" msgid="1604522770162810570">"Ukurekhoda isikrini kususiwe"</string>
     <string name="screenrecord_delete_error" msgid="2870506119743013588">"Iphutha lokususa ukurekhoda isikrini"</string>
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"Yehlulekile ukuthola izimvume"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
@@ -1069,8 +1067,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>
-    <!-- no translation found for controls_media_dismiss_button (9081375542265132213) -->
-    <skip />
+    <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>
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/config.xml b/packages/SystemUI/res/values/config.xml
index ce1ca5a..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-->
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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 823c1ff..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] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ecf1c2c9..5ad8cad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,6 +22,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ClockPlugin;
@@ -36,6 +37,7 @@
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
+@KeyguardStatusViewScope
 public class KeyguardClockSwitch extends RelativeLayout {
 
     private static final String TAG = "KeyguardClockSwitch";
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f17f1ca..fe5fcc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -34,10 +34,11 @@
 public class KeyguardClockSwitchController {
     private static final boolean CUSTOM_CLOCKS_ENABLED = true;
 
+    private final KeyguardClockSwitch mView;
     private final StatusBarStateController mStatusBarStateController;
     private final SysuiColorExtractor mColorExtractor;
     private final ClockManager mClockManager;
-    private KeyguardClockSwitch mView;
+    private final KeyguardSliceViewController mKeyguardSliceViewController;
 
     private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
@@ -52,9 +53,13 @@
      *
      * The color palette changes when the wallpaper is changed.
      */
-    private final ColorExtractor.OnColorsChangedListener mColorsListener = (extractor, which) -> {
-        if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-            mView.updateColors(getGradientColors());
+    private final ColorExtractor.OnColorsChangedListener mColorsListener =
+            new ColorExtractor.OnColorsChangedListener() {
+        @Override
+        public void onColorsChanged(ColorExtractor extractor, int which) {
+            if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+                mView.updateColors(getGradientColors());
+            }
         }
     };
 
@@ -84,22 +89,27 @@
     };
 
     @Inject
-    public KeyguardClockSwitchController(StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor, ClockManager clockManager) {
+    public KeyguardClockSwitchController(KeyguardClockSwitch keyguardClockSwitch,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor, ClockManager clockManager,
+            KeyguardSliceViewController keyguardSliceViewController) {
+        mView = keyguardClockSwitch;
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
         mClockManager = clockManager;
+        mKeyguardSliceViewController = keyguardSliceViewController;
     }
 
     /**
      * Attach the controller to the view it relates to.
      */
-    public void attach(KeyguardClockSwitch view) {
-        mView = view;
+    public void init() {
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
         mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+
+        mKeyguardSliceViewController.init();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 6f19613..be21d20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -34,12 +34,15 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
 
+import javax.inject.Inject;
+
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
@@ -47,6 +50,7 @@
     private final MediaRouter mMediaRouter;
     private final DisplayManager mDisplayService;
     private final InjectionInflationController mInjectableInflater;
+    private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
 
     private boolean mShowing;
@@ -86,10 +90,13 @@
         }
     };
 
+    @Inject
     public KeyguardDisplayManager(Context context,
-            InjectionInflationController injectableInflater) {
+            InjectionInflationController injectableInflater,
+            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
         mContext = context;
         mInjectableInflater = injectableInflater;
+        mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mMediaRouter = mContext.getSystemService(MediaRouter.class);
         mDisplayService = mContext.getSystemService(DisplayManager.class);
         mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
@@ -124,6 +131,7 @@
         Presentation presentation = mPresentations.get(displayId);
         if (presentation == null) {
             final Presentation newPresentation = new KeyguardPresentation(mContext, display,
+                    mKeyguardStatusViewComponentFactory,
                     mInjectableInflater.injectable(LayoutInflater.from(mContext)));
             newPresentation.setOnDismissListener(dialog -> {
                 if (newPresentation.equals(mPresentations.get(displayId))) {
@@ -241,7 +249,9 @@
     static final class KeyguardPresentation extends Presentation {
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
+        private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
         private final LayoutInflater mInjectableLayoutInflater;
+        private KeyguardClockSwitchController mKeyguardClockSwitchController;
         private View mClock;
         private int mUsableWidth;
         private int mUsableHeight;
@@ -259,8 +269,10 @@
         };
 
         KeyguardPresentation(Context context, Display display,
+                KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
                 LayoutInflater injectionLayoutInflater) {
             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
+            mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
             mInjectableLayoutInflater = injectionLayoutInflater;
             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
             setCancelable(false);
@@ -302,6 +314,12 @@
 
             // Avoid screen burn in
             mClock.post(mMoveTextRunnable);
+
+            mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory
+                    .build(findViewById(R.id.clock))
+                    .getKeyguardClockSwitchController();
+
+            mKeyguardClockSwitchController.init();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5f00a59..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().createViewInstanceCreatorFactory());
+            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/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index f639c88..a479bca 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -16,12 +16,6 @@
 
 package com.android.keyguard;
 
-import static android.app.slice.Slice.HINT_LIST_ITEM;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -35,28 +29,19 @@
 import android.graphics.text.LineBreaker;
 import android.net.Uri;
 import android.os.Trace;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.TypedValue;
-import android.view.Display;
 import android.view.View;
 import android.view.animation.Animation;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
-import androidx.slice.Slice;
 import androidx.slice.SliceItem;
-import androidx.slice.SliceViewManager;
 import androidx.slice.core.SliceQuery;
-import androidx.slice.widget.ListContent;
 import androidx.slice.widget.RowContent;
 import androidx.slice.widget.SliceContent;
-import androidx.slice.widget.SliceLiveData;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -64,70 +49,49 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
+import java.util.Map;
 
 /**
  * View visible under the clock on the lock screen and AoD.
  */
-public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
-        Observer<Slice>, TunerService.Tunable, ConfigurationController.ConfigurationListener {
+public class KeyguardSliceView extends LinearLayout {
 
     private static final String TAG = "KeyguardSliceView";
     public static final int DEFAULT_ANIM_DURATION = 550;
 
-    private final HashMap<View, PendingIntent> mClickActions;
-    private final ActivityStarter mActivityStarter;
-    private final ConfigurationController mConfigurationController;
     private final LayoutTransition mLayoutTransition;
-    private final TunerService mTunerService;
-    private Uri mKeyguardSliceUri;
     @VisibleForTesting
     TextView mTitle;
     private Row mRow;
     private int mTextColor;
     private float mDarkAmount = 0;
 
-    private LiveData<Slice> mLiveData;
-    private int mDisplayId = INVALID_DISPLAY;
     private int mIconSize;
     private int mIconSizeWithHeader;
     /**
      * Runnable called whenever the view contents change.
      */
     private Runnable mContentChangeListener;
-    private Slice mSlice;
     private boolean mHasHeader;
     private final int mRowWithHeaderPadding;
     private final int mRowPadding;
     private float mRowTextSize;
     private float mRowWithHeaderTextSize;
+    private View.OnClickListener mOnClickListener;
 
-    @Inject
-    public KeyguardSliceView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            ActivityStarter activityStarter, ConfigurationController configurationController,
-            TunerService tunerService, @Main Resources resources) {
+    public KeyguardSliceView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mTunerService = tunerService;
-        mClickActions = new HashMap<>();
+        Resources resources = context.getResources();
         mRowPadding = resources.getDimensionPixelSize(R.dimen.subtitle_clock_padding);
         mRowWithHeaderPadding = resources.getDimensionPixelSize(R.dimen.header_subtitle_padding);
-        mActivityStarter = activityStarter;
-        mConfigurationController = configurationController;
 
         mLayoutTransition = new LayoutTransition();
         mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2);
@@ -153,39 +117,10 @@
                 R.dimen.widget_label_font_size);
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
-        mTitle.setOnClickListener(this);
         mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        Display display = getDisplay();
-        if (display != null) {
-            mDisplayId = display.getDisplayId();
-        }
-        mTunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
-        // Make sure we always have the most current slice
-        if (mDisplayId == DEFAULT_DISPLAY) {
-            mLiveData.observeForever(this);
-        }
-        mConfigurationController.addCallback(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        // TODO(b/117344873) Remove below work around after this issue be fixed.
-        if (mDisplayId == DEFAULT_DISPLAY) {
-            mLiveData.removeObserver(this);
-        }
-        mTunerService.removeTunable(this);
-        mConfigurationController.removeCallback(this);
-    }
-
-    @Override
     public void onVisibilityAggregated(boolean isVisible) {
         super.onVisibilityAggregated(isVisible);
         setLayoutTransition(isVisible ? mLayoutTransition : null);
@@ -198,44 +133,31 @@
         return mHasHeader;
     }
 
-    private void showSlice() {
-        Trace.beginSection("KeyguardSliceView#showSlice");
-        if (mSlice == null) {
-            mTitle.setVisibility(GONE);
-            mRow.setVisibility(GONE);
-            mHasHeader = false;
-            if (mContentChangeListener != null) {
-                mContentChangeListener.run();
-            }
-            Trace.endSection();
-            return;
+    void hideSlice() {
+        mTitle.setVisibility(GONE);
+        mRow.setVisibility(GONE);
+        mHasHeader = false;
+        if (mContentChangeListener != null) {
+            mContentChangeListener.run();
         }
-        mClickActions.clear();
+    }
 
-        ListContent lc = new ListContent(getContext(), mSlice);
-        SliceContent headerContent = lc.getHeader();
-        mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
-        List<SliceContent> subItems = new ArrayList<>();
-        for (int i = 0; i < lc.getRowItems().size(); i++) {
-            SliceContent subItem = lc.getRowItems().get(i);
-            String itemUri = subItem.getSliceItem().getSlice().getUri().toString();
-            // Filter out the action row
-            if (!KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri)) {
-                subItems.add(subItem);
-            }
-        }
+    Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
+        Trace.beginSection("KeyguardSliceView#showSlice");
+        mHasHeader = header != null;
+        Map<View, PendingIntent> clickActions = new HashMap<>();
+
         if (!mHasHeader) {
             mTitle.setVisibility(GONE);
         } else {
             mTitle.setVisibility(VISIBLE);
 
-            RowContent header = lc.getHeader();
             SliceItem mainTitle = header.getTitleItem();
             CharSequence title = mainTitle != null ? mainTitle.getText() : null;
             mTitle.setText(title);
             if (header.getPrimaryAction() != null
                     && header.getPrimaryAction().getAction() != null) {
-                mClickActions.put(mTitle, header.getPrimaryAction().getAction());
+                clickActions.put(mTitle, header.getPrimaryAction().getAction());
             }
         }
 
@@ -265,7 +187,7 @@
             if (rc.getPrimaryAction() != null) {
                 pendingIntent = rc.getPrimaryAction().getAction();
             }
-            mClickActions.put(button, pendingIntent);
+            clickActions.put(button, pendingIntent);
 
             final SliceItem titleItem = rc.getTitleItem();
             button.setText(titleItem == null ? null : titleItem.getText());
@@ -286,14 +208,14 @@
                 }
             }
             button.setCompoundDrawables(iconDrawable, null, null, null);
-            button.setOnClickListener(this);
+            button.setOnClickListener(mOnClickListener);
             button.setClickable(pendingIntent != null);
         }
 
         // Removing old views
         for (int i = 0; i < mRow.getChildCount(); i++) {
             View child = mRow.getChildAt(i);
-            if (!mClickActions.containsKey(child)) {
+            if (!clickActions.containsKey(child)) {
                 mRow.removeView(child);
                 i--;
             }
@@ -303,6 +225,8 @@
             mContentChangeListener.run();
         }
         Trace.endSection();
+
+        return clickActions;
     }
 
     public void setDarkAmount(float darkAmount) {
@@ -323,14 +247,6 @@
         }
     }
 
-    @Override
-    public void onClick(View v) {
-        final PendingIntent action = mClickActions.get(v);
-        if (action != null) {
-            mActivityStarter.startPendingIntentDismissingKeyguard(action);
-        }
-    }
-
     /**
      * Runnable that gets invoked every time the title or the row visibility changes.
      * @param contentChangeListener The listener.
@@ -339,43 +255,6 @@
         mContentChangeListener = contentChangeListener;
     }
 
-    /**
-     * LiveData observer lifecycle.
-     * @param slice the new slice content.
-     */
-    @Override
-    public void onChanged(Slice slice) {
-        mSlice = slice;
-        showSlice();
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        setupUri(newValue);
-    }
-
-    /**
-     * Sets the slice provider Uri.
-     */
-    public void setupUri(String uriString) {
-        if (uriString == null) {
-            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
-        }
-
-        boolean wasObserving = false;
-        if (mLiveData != null && mLiveData.hasActiveObservers()) {
-            wasObserving = true;
-            mLiveData.removeObserver(this);
-        }
-
-        mKeyguardSliceUri = Uri.parse(uriString);
-        mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
-
-        if (wasObserving) {
-            mLiveData.observeForever(this);
-        }
-    }
-
     @VisibleForTesting
     int getTextColor() {
         return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
@@ -387,8 +266,7 @@
         updateTextColors();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
+    void onDensityOrFontScaleChanged() {
         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
         mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size);
         mRowTextSize = mContext.getResources().getDimensionPixelSize(
@@ -397,37 +275,21 @@
                 R.dimen.header_row_font_size);
     }
 
-    public void refresh() {
-        Slice slice;
-        Trace.beginSection("KeyguardSliceView#refresh");
-        // We can optimize performance and avoid binder calls when we know that we're bound
-        // to a Slice on the same process.
-        if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
-            KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
-            if (instance != null) {
-                slice = instance.onBindSlice(mKeyguardSliceUri);
-            } else {
-                Log.w(TAG, "Keyguard slice not bound yet?");
-                slice = null;
-            }
-        } else {
-            slice = SliceViewManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
-        }
-        onChanged(slice);
-        Trace.endSection();
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardSliceView:");
-        pw.println("  mClickActions: " + mClickActions);
         pw.println("  mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE));
         pw.println("  mRow: " + (mRow == null ? "null" : mRow.getVisibility() == VISIBLE));
         pw.println("  mTextColor: " + Integer.toHexString(mTextColor));
         pw.println("  mDarkAmount: " + mDarkAmount);
-        pw.println("  mSlice: " + mSlice);
         pw.println("  mHasHeader: " + mHasHeader);
     }
 
+    @Override
+    public void setOnClickListener(View.OnClickListener onClickListener) {
+        mOnClickListener = onClickListener;
+        mTitle.setOnClickListener(onClickListener);
+    }
+
     public static class Row extends LinearLayout {
 
         /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
new file mode 100644
index 0000000..2470b95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.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.keyguard;
+
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.Trace;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.slice.Slice;
+import androidx.slice.SliceViewManager;
+import androidx.slice.widget.ListContent;
+import androidx.slice.widget.RowContent;
+import androidx.slice.widget.SliceContent;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.keyguard.dagger.KeyguardStatusViewScope;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/** Controller for a {@link KeyguardSliceView}. */
+@KeyguardStatusViewScope
+public class KeyguardSliceViewController implements Dumpable {
+    private static final String TAG = "KeyguardSliceViewCtrl";
+
+    private final KeyguardSliceView mView;
+    private final KeyguardStatusView mKeyguardStatusView;
+    private final ActivityStarter mActivityStarter;
+    private final ConfigurationController mConfigurationController;
+    private final TunerService mTunerService;
+    private final DumpManager mDumpManager;
+    private int mDisplayId;
+    private LiveData<Slice> mLiveData;
+    private Uri mKeyguardSliceUri;
+    private Slice mSlice;
+    private Map<View, PendingIntent> mClickActions;
+
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+
+                @Override
+                public void onViewAttachedToWindow(View v) {
+
+                    Display display = mView.getDisplay();
+                    if (display != null) {
+                        mDisplayId = display.getDisplayId();
+                    }
+                    mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
+                    // Make sure we always have the most current slice
+                    if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
+                        mLiveData.observeForever(mObserver);
+                    }
+                    mConfigurationController.addCallback(mConfigurationListener);
+                    mDumpManager.registerDumpable(
+                            TAG + "@" + Integer.toHexString(
+                                    KeyguardSliceViewController.this.hashCode()),
+                            KeyguardSliceViewController.this);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+
+                    // TODO(b/117344873) Remove below work around after this issue be fixed.
+                    if (mDisplayId == DEFAULT_DISPLAY) {
+                        mLiveData.removeObserver(mObserver);
+                    }
+                    mTunerService.removeTunable(mTunable);
+                    mConfigurationController.removeCallback(mConfigurationListener);
+                    mDumpManager.unregisterDumpable(TAG);
+                }
+            };
+
+    TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
+
+    ConfigurationController.ConfigurationListener mConfigurationListener =
+            new ConfigurationController.ConfigurationListener() {
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.onDensityOrFontScaleChanged();
+        }
+    };
+
+    Observer<Slice> mObserver = new Observer<Slice>() {
+        @Override
+        public void onChanged(Slice slice) {
+            mSlice = slice;
+            showSlice(slice);
+        }
+    };
+
+    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final PendingIntent action = mClickActions.get(v);
+            if (action != null && mActivityStarter != null) {
+                mActivityStarter.startPendingIntentDismissingKeyguard(action);
+            }
+        }
+    };
+
+    @Inject
+    public KeyguardSliceViewController(KeyguardSliceView keyguardSliceView,
+            KeyguardStatusView keyguardStatusView, ActivityStarter activityStarter,
+            ConfigurationController configurationController, TunerService tunerService,
+            DumpManager dumpManager) {
+        mView = keyguardSliceView;
+        mKeyguardStatusView = keyguardStatusView;
+        mActivityStarter = activityStarter;
+        mConfigurationController = configurationController;
+        mTunerService = tunerService;
+        mDumpManager = dumpManager;
+    }
+
+    /** Initialize the controller. */
+    public void init() {
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        mView.setOnClickListener(mOnClickListener);
+        // TODO: remove the line below.
+        mKeyguardStatusView.setKeyguardSliceViewController(this);
+    }
+
+    /**
+     * Sets the slice provider Uri.
+     */
+    public void setupUri(String uriString) {
+        if (uriString == null) {
+            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+        }
+
+        boolean wasObserving = false;
+        if (mLiveData != null && mLiveData.hasActiveObservers()) {
+            wasObserving = true;
+            mLiveData.removeObserver(mObserver);
+        }
+
+        mKeyguardSliceUri = Uri.parse(uriString);
+        mLiveData = SliceLiveData.fromUri(mView.getContext(), mKeyguardSliceUri);
+
+        if (wasObserving) {
+            mLiveData.observeForever(mObserver);
+        }
+    }
+
+    /**
+     * Update contents of the view.
+     */
+    public void refresh() {
+        Slice slice;
+        Trace.beginSection("KeyguardSliceViewController#refresh");
+        // We can optimize performance and avoid binder calls when we know that we're bound
+        // to a Slice on the same process.
+        if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
+            KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
+            if (instance != null) {
+                slice = instance.onBindSlice(mKeyguardSliceUri);
+            } else {
+                Log.w(TAG, "Keyguard slice not bound yet?");
+                slice = null;
+            }
+        } else {
+            // TODO: Make SliceViewManager injectable
+            slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(mKeyguardSliceUri);
+        }
+        mObserver.onChanged(slice);
+        Trace.endSection();
+    }
+
+    void showSlice(Slice slice) {
+        Trace.beginSection("KeyguardSliceViewController#showSlice");
+        if (slice == null) {
+            mView.hideSlice();
+            Trace.endSection();
+            return;
+        }
+
+        ListContent lc = new ListContent(slice);
+        RowContent headerContent = lc.getHeader();
+        boolean hasHeader =
+                headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
+
+        List<SliceContent> subItems = lc.getRowItems().stream().filter(sliceContent -> {
+            String itemUri = sliceContent.getSliceItem().getSlice().getUri().toString();
+            // Filter out the action row
+            return !KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri);
+        }).collect(Collectors.toList());
+
+
+        mClickActions = mView.showSlice(hasHeader ? headerContent : null, subItems);
+
+        Trace.endSection();
+    }
+
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("  mSlice: " + mSlice);
+        pw.println("  mClickActions: " + mClickActions);
+
+        mKeyguardStatusView.dump(fd, pw, args);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 4c6aafb..6e11174 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -32,7 +32,6 @@
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.GridLayout;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.core.graphics.ColorUtils;
@@ -56,7 +55,6 @@
     private final LockPatternUtils mLockPatternUtils;
     private final IActivityManager mIActivityManager;
 
-    private LinearLayout mStatusViewContainer;
     private TextView mLogoutView;
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
@@ -64,6 +62,7 @@
     private View mNotificationIcons;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
+    private KeyguardSliceViewController mKeyguardSliceViewController;
 
     private boolean mPulsing;
     private float mDarkAmount = 0;
@@ -179,7 +178,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mStatusViewContainer = findViewById(R.id.status_view_container);
         mLogoutView = findViewById(R.id.logout);
         mNotificationIcons = findViewById(R.id.clock_notification_icon_container);
         if (mLogoutView != null) {
@@ -250,7 +248,7 @@
 
     public void dozeTimeTick() {
         refreshTime();
-        mKeyguardSlice.refresh();
+        mKeyguardSliceViewController.refresh();
     }
 
     private void refreshTime() {
@@ -456,4 +454,9 @@
             Log.e(TAG, "Failed to logout user", re);
         }
     }
+
+    // TODO: remove this method when a controller is available.
+    void setKeyguardSliceViewController(KeyguardSliceViewController keyguardSliceViewController) {
+        mKeyguardSliceViewController = keyguardSliceViewController;
+    }
 }
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/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
new file mode 100644
index 0000000..21ccff7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -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.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardStatusView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ */
+@Subcomponent(modules = {KeyguardStatusViewModule.class})
+@KeyguardStatusViewScope
+public interface KeyguardStatusViewComponent {
+    /** Simple factory for {@link KeyguardStatusViewComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardStatusViewComponent build(@BindsInstance KeyguardStatusView presentation);
+    }
+
+    /** Builds a {@link com.android.keyguard.KeyguardClockSwitchController}. */
+    KeyguardClockSwitchController getKeyguardClockSwitchController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
new file mode 100644
index 0000000..1d51e59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -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.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardSliceView;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.R;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusViewComponent}. */
+@Module
+public abstract class KeyguardStatusViewModule {
+    @Provides
+    static KeyguardClockSwitch getKeyguardClockSwitch(KeyguardStatusView keyguardPresentation) {
+        return keyguardPresentation.findViewById(R.id.keyguard_clock_container);
+    }
+
+    @Provides
+    static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
+        return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
rename to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
index 114c30e..880822a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.keyguard.dagger;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
-import javax.inject.Qualifier;
+import javax.inject.Scope;
 
-@Qualifier
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
 @Documented
 @Retention(RUNTIME)
-public @interface PipMenuActivityClass {
-}
+@Scope
+public @interface KeyguardStatusViewScope {}
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/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index c04775a..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;
@@ -64,7 +65,6 @@
 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.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -77,7 +77,7 @@
 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;
@@ -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 5f88156..d859a63 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -24,20 +24,16 @@
 
 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_SYSTEM_ALERT_WINDOW};
 
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 1515272..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";
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/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index d1149d3..f4c865e 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -21,7 +21,7 @@
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -38,7 +38,7 @@
  *
  * NOTE: Clients of this class should take care to pass in the correct user context when querying
  * settings, otherwise you will always read/write for user 0 which is almost never what you want.
- * See {@link CurrentUserContextTracker} for a simple way to get the current context
+ * See {@link UserContextProvider} for a simple way to get the current context
  */
 public final class Prefs {
     private Prefs() {} // no instantation
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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d17ca404..e91284b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.NonNull;
-import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.RectF;
 import android.os.Handler;
@@ -115,24 +116,24 @@
     private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
 
     public SwipeHelper(
-            int swipeDirection, Callback callback, Context context, FalsingManager falsingManager) {
+            int swipeDirection, Callback callback, Resources resources,
+            ViewConfiguration viewConfiguration, FalsingManager falsingManager) {
         mCallback = callback;
         mHandler = new Handler();
         mSwipeDirection = swipeDirection;
         mVelocityTracker = VelocityTracker.obtain();
-        final ViewConfiguration configuration = ViewConfiguration.get(context);
-        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
-        mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+        mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
+        mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
 
         // Extra long-press!
         mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
 
-        Resources res = context.getResources();
-        mDensityScale =  res.getDisplayMetrics().density;
-        mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
-        mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
+        mDensityScale =  resources.getDisplayMetrics().density;
+        mFalsingThreshold = resources.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
+        mFadeDependingOnAmountSwiped = resources.getBoolean(
+                R.bool.config_fadeDependingOnAmountSwiped);
         mFalsingManager = falsingManager;
-        mFlingAnimationUtils = new FlingAnimationUtils(res.getDisplayMetrics(),
+        mFlingAnimationUtils = new FlingAnimationUtils(resources.getDisplayMetrics(),
                 getMaxEscapeAnimDuration() / 1000f);
     }
 
@@ -697,14 +698,15 @@
         float translation = getTranslation(mCurrView);
         return ev.getActionMasked() == MotionEvent.ACTION_UP
                 && !mFalsingManager.isUnlockingDisabled()
-                && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
+                && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
                 && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
     }
 
-    public boolean isFalseGesture(MotionEvent ev) {
+    /** Returns true if the gesture should be rejected. */
+    public boolean isFalseGesture() {
         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
         if (mFalsingManager.isClassifierEnabled()) {
-            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
+            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(NOTIFICATION_DISMISS);
         } else {
             falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 627f559..bb7906e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -29,7 +29,7 @@
 import androidx.core.app.AppComponentFactory;
 
 import com.android.systemui.dagger.ContextComponentHelper;
-import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -65,7 +65,7 @@
             ((ContextInitializer) app).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
+                        SystemUIFactory.getInstance().getSysUIComponent().inject(
                                 SystemUIAppComponentFactory.this);
                     }
             );
@@ -85,8 +85,8 @@
             ((ContextInitializer) contentProvider).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        GlobalRootComponent rootComponent =
-                                SystemUIFactory.getInstance().getRootComponent();
+                        SysUIComponent rootComponent =
+                                SystemUIFactory.getInstance().getSysUIComponent();
                         try {
                             Method injectMethod = rootComponent.getClass()
                                     .getMethod("inject", contentProvider.getClass());
@@ -111,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);
@@ -129,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);
@@ -147,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 4f78f65..7dcec3d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -33,6 +33,7 @@
 
 import com.android.systemui.dagger.ContextComponentHelper;
 import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.NotificationChannels;
 
@@ -58,6 +59,7 @@
     private boolean mServicesStarted;
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
     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 e34a653..941fd6f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,26 +27,18 @@
 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.DaggerGlobalRootComponent;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
-import com.android.systemui.demomode.DemoModeController;
 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.ExecutionException;
 import java.util.concurrent.Executor;
 
 /**
@@ -92,15 +84,20 @@
 
     public SystemUIFactory() {}
 
-    private void init(Context context) {
+    private void init(Context context) throws ExecutionException, InterruptedException {
         mRootComponent = buildGlobalRootComponent(context);
+        // Stand up WMComponent
         mWMComponent = mRootComponent.getWMComponentBuilder().build();
-        // TODO: use WMComponent to pass APIs into the SysUIComponent.
-        mSysUIComponent = mRootComponent.getSysUIComponent().build();
+
+        // And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
+        // TODO: StubAPIClass is just a placeholder.
+        mSysUIComponent = mRootComponent.getSysUIComponent()
+                .setStubAPIClass(mWMComponent.createStubAPIClass())
+                .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();
     }
 
@@ -110,10 +107,19 @@
                 .build();
     }
 
+
     public GlobalRootComponent getRootComponent() {
         return mRootComponent;
     }
 
+    public WMComponent getWMComponent() {
+        return mWMComponent;
+    }
+
+    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);
@@ -147,19 +153,4 @@
                 Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
-
-    public NotificationIconAreaController createNotificationIconAreaController(Context context,
-            StatusBar statusBar,
-            NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController,
-            DemoModeController demoModeController) {
-        return new NotificationIconAreaController(context, statusBar, statusBarStateController,
-                wakeUpCoordinator, keyguardBypassController,
-                Dependency.get(NotificationMediaManager.class),
-                Dependency.get(NotificationListener.class),
-                Dependency.get(DozeParameters.class),
-                Dependency.get(BubbleController.class),
-                demoModeController);
-    }
 }
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 1752579..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";
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d5f74a8..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";
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 8187a223..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,7 @@
 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;
@@ -33,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;
@@ -44,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
@@ -52,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 {
@@ -66,6 +67,13 @@
 
     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<>();
@@ -83,8 +91,10 @@
     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
     };
@@ -105,6 +115,7 @@
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
         mAudioManager = audioManager;
+        mLocationManager = context.getSystemService(LocationManager.class);
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -288,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
@@ -303,7 +334,13 @@
         // 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_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;
         }
 
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/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/appops/dagger/AppOpsModule.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
copy to packages/SystemUI/src/com/android/systemui/appops/dagger/AppOpsModule.java
index fe5fa2b..d4cc3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/dagger/AppOpsModule.java
@@ -14,23 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.onehanded.dagger;
+package com.android.systemui.appops.dagger;
 
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
+import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.appops.AppOpsControllerImpl;
 
 import dagger.Binds;
 import dagger.Module;
 
-/**
- * Dagger Module for One handed.
- */
+/** Dagger Module for code in the appops package. */
 @Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
+public interface AppOpsModule {
+    /** */
     @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
+    AppOpsController provideAppOpsController(AppOpsControllerImpl controllerImpl);
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 2df48fc..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.shared.system.QuickStepContract;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shared.system.QuickStepContract;
 
 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/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 0dc06f2..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.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/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 722e3124..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.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/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ade106f..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);
+                    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 d4e6506b..0892612 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -111,7 +111,7 @@
             // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
             // Gatekeeper Password and operationId.
             mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
-                    password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_RETURN_GK_PW,
+                    password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
                     this::onCredentialVerified);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index ad89ae8..ab8162f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -75,7 +75,7 @@
                         mLockPatternUtils,
                         credential,
                         mEffectiveUserId,
-                        LockPatternUtils.VERIFY_FLAG_RETURN_GK_PW,
+                        LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
                         this::onPatternVerified);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index a8f6f85..b44ff29 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -292,10 +292,12 @@
             // 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 VerifyCredentialResponse gkResponse = mLockPatternUtils.verifyGatekeeperPassword(
-                    response.getGatekeeperPw(), mOperationId, mEffectiveUserId);
+            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 e3126b8..82fb808 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -29,16 +30,18 @@
 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;
@@ -47,7 +50,7 @@
  * 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;
@@ -56,12 +59,13 @@
     private final WindowManager mWindowManager;
     private final ContentResolver mContentResolver;
     private final Handler mHandler;
-    private final UdfpsView mView;
     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
@@ -86,27 +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 = view.isValidTouch(event.getX(), event.getY(),
+                final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(),
                         event.getPressure());
-                if (!view.isFingerDown() && isValidTouch) {
+                if (!isFingerDown && isValidTouch) {
                     onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(),
                             event.getTouchMajor());
-                } else if (view.isFingerDown() && !isValidTouch) {
+                } else if (isFingerDown && !isValidTouch) {
                     onFingerUp();
                 }
                 return true;
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (view.isFingerDown()) {
+                if (isFingerDown) {
                     onFingerUp();
                 }
                 return true;
@@ -116,23 +126,24 @@
         }
     };
 
-    UdfpsController(Context context) {
+    UdfpsController(@NonNull Context context,
+            @NonNull StatusBarStateController statusBarStateController) {
         mFingerprintManager = context.getSystemService(FingerprintManager.class);
         mWindowManager = context.getSystemService(WindowManager.class);
         mContentResolver = context.getContentResolver();
         mHandler = new Handler(Looper.getMainLooper());
-
         mLayoutParams = createLayoutParams(context);
-        LinearLayout layout = new LinearLayout(context);
-        layout.setLayoutParams(mLayoutParams);
-        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, layout,
-                false);
-        mView.setOnTouchListener(mOnTouchListener);
+
+        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
 
         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(
@@ -166,27 +177,39 @@
         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");
             }
         });
     }
@@ -219,26 +242,33 @@
 
     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());
+            }
         }
     }
 
@@ -254,11 +284,12 @@
                 // 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.windowAnimations = 0;
+        lp.setFitInsetsTypes(0);
         return lp;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index d4e86d5..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,13 +94,17 @@
                         "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 */);
@@ -83,8 +112,17 @@
 
         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,36 +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);
     }
 
     void setScrimAlpha(int alpha) {
         mScrimPaint.setAlpha(alpha);
     }
 
-    boolean isFingerDown() {
-        return mIsFingerDown;
+    boolean isScrimShowing() {
+        return mIsScrimShowing;
     }
 
-    void onFingerDown() {
-        mIsFingerDown = true;
+    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/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 fa0d2ba..80150c9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -305,7 +305,7 @@
 
         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);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index ec9644a..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;
 
@@ -556,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());
                 }
             }
@@ -582,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.
@@ -637,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);
                 }
@@ -684,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 {
@@ -728,7 +719,7 @@
             mFlyout.removeCallbacks(mHideFlyout);
             animateFlyoutCollapsed(shouldDismiss, velX);
 
-            maybeShowStackUserEducation();
+            maybeShowStackEdu();
         }
     };
 
@@ -737,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;
@@ -805,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
@@ -819,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);
@@ -1092,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();
         }
     }
 
@@ -1164,9 +1167,9 @@
      */
     public void onThemeChanged() {
         setUpFlyout();
-        setUpUserEducation();
         setUpManageMenu();
         updateOverflow();
+        updateUserEdu();
         updateExpandedViewTheme();
     }
 
@@ -1197,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);
     }
@@ -1446,7 +1448,7 @@
             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.getStartPosition());
@@ -1649,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.getStartPosition();
-                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
@@ -1791,7 +1684,9 @@
         cancelDelayedExpandCollapseSwitchAnimations();
 
         mIsExpanded = true;
-        hideStackUserEducation(true /* fromExpansion */);
+        if (mStackEduView != null) {
+            mStackEduView.hide(true /* fromExpansion */);
+        }
         beforeExpandedViewAnimation();
 
         mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -1799,7 +1694,9 @@
         updatePointerPosition();
         mExpandedAnimationController.expandFromStack(() -> {
             afterExpandedViewAnimation();
-            maybeShowManageEducation(true);
+            if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+                maybeShowManageEdu();
+            }
         } /* after */);
 
         mExpandedViewContainer.setTranslationX(0);
@@ -1936,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");
@@ -2104,8 +2003,8 @@
         // from any location.
         if (!mIsExpanded
                 || mShowingManage
-                || (mManageEducationView != null
-                    && mManageEducationView.getVisibility() == VISIBLE)) {
+                || (mManageEduView != null
+                    && mManageEduView.getVisibility() == VISIBLE)) {
             touchableRegion.setEmpty();
         }
     }
@@ -2289,7 +2188,7 @@
         if (flyoutMessage == null
                 || flyoutMessage.message == null
                 || !bubble.showFlyout()
-                || (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE)
+                || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE)
                 || isExpanded()
                 || mIsExpansionAnimating
                 || mIsGestureInProgress
@@ -2398,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;
@@ -2770,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<>();
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/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index fcb215c..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,6 +25,7 @@
 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;
@@ -40,8 +41,6 @@
 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/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index 646e620..6961b45 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -70,7 +70,7 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         return mIsFalseTouch;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index cc64fb5..decaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -262,7 +262,7 @@
     /**
      * @return true if the classifier determined that this is not a human interacting with the phone
      */
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (FalsingLog.ENABLED) {
             // We're getting some false wtfs from touches that happen after the device went
             // to sleep. Only report missing sessions that happen when the device is interactive.
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index f35322b..2c31862 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";
@@ -187,8 +187,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
-        return mInternalFalsingManager.isFalseTouch();
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        return mInternalFalsingManager.isFalseTouch(interactionType);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index a50f9ce..9d847ca 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -189,7 +189,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        mDataProvider.setInteractionType(interactionType);
         if (!mDataProvider.isDirty()) {
             return mPreviousResult;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
index ea46441..8d06748 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
@@ -116,7 +116,10 @@
      * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
      */
     final void setInteractionType(@Classifier.InteractionType int interactionType) {
-        this.mInteractionType = interactionType;
+        if (mInteractionType != interactionType) {
+            mInteractionType = interactionType;
+            mDirty = true;
+        }
     }
 
     public boolean isDirty() {
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
index cca0f16..ed60849 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
@@ -19,8 +19,8 @@
 import android.content.ComponentName
 import android.graphics.drawable.Icon
 import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Icon cache for custom icons sent with controls.
@@ -28,7 +28,7 @@
  * It assumes that only one component can be current at the time, to minimize the number of icons
  * stored at a given time.
  */
-@Singleton
+@SysUISingleton
 class CustomIconCache @Inject constructor() {
 
     private var currentComponent: ComponentName? = null
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/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/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index e15380b..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,
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 5f75c96..3710310 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -52,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
@@ -62,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,
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/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
deleted file mode 100644
index e2a6d6c..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ /dev/null
@@ -1,268 +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.dagger;
-
-import com.android.systemui.ActivityStarterDelegate;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.appops.AppOpsControllerImpl;
-import com.android.systemui.classifier.FalsingManagerProxy;
-import com.android.systemui.controls.dagger.ControlsModule;
-import com.android.systemui.globalactions.GlobalActionsComponent;
-import com.android.systemui.globalactions.GlobalActionsImpl;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.PowerNotificationWarnings;
-import com.android.systemui.power.PowerUI;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
-import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastControllerImpl;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.LocationControllerImpl;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.SecurityControllerImpl;
-import com.android.systemui.statusbar.policy.SensorPrivacyController;
-import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerServiceImpl;
-import com.android.systemui.util.RingerModeTracker;
-import com.android.systemui.util.RingerModeTrackerImpl;
-import com.android.systemui.volume.VolumeComponent;
-import com.android.systemui.volume.VolumeDialogComponent;
-import com.android.systemui.volume.VolumeDialogControllerImpl;
-
-import dagger.Binds;
-import dagger.Module;
-
-/**
- * Maps interfaces to implementations for use with Dagger.
- */
-@Module(includes = {ControlsModule.class})
-public abstract class DependencyBinder {
-
-    /**
-     */
-    @Binds
-    public abstract ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate);
-
-    /**
-     */
-    @Binds
-    public abstract BluetoothController provideBluetoothController(
-            BluetoothControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
-            GlobalActionsComponent controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract LocationController provideLocationController(
-            LocationControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract RotationLockController provideRotationLockController(
-            RotationLockControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract NetworkController provideNetworkController(
-            NetworkControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract ZenModeController provideZenModeController(
-            ZenModeControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract HotspotController provideHotspotController(
-            HotspotControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract AppOpsController provideAppOpsController(
-            AppOpsControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract NotificationRemoteInputManager.Callback provideNotificationRemoteInputManager(
-            StatusBarRemoteInputCallback callbackImpl);
-
-    /**
-     */
-    @Binds
-    public abstract CastController provideCastController(CastControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract FlashlightController provideFlashlightController(
-            FlashlightControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract KeyguardStateController provideKeyguardMonitor(
-            KeyguardStateControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract UserInfoController provideUserInfoContrller(
-            UserInfoControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract ManagedProfileController provideManagedProfileController(
-            ManagedProfileControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract NextAlarmController provideNextAlarmController(
-            NextAlarmControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract SecurityController provideSecurityController(
-            SecurityControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract TunerService provideTunerService(TunerServiceImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract DarkIconDispatcher provideDarkIconDispatcher(
-            DarkIconDispatcherImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract StatusBarStateController provideStatusBarStateController(
-            StatusBarStateControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract SysuiStatusBarStateController providesSysuiStatusBarStateController(
-            StatusBarStateControllerImpl statusBarStateControllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract StatusBarIconController provideStatusBarIconController(
-            StatusBarIconControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract ExtensionController provideExtensionController(
-            ExtensionControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract VolumeDialogController provideVolumeDialogController(
-            VolumeDialogControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract SensorPrivacyController provideSensorPrivacyControllerImpl(
-            SensorPrivacyControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract QSHost provideQsHost(QSTileHost controllerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
-
-    /**
-     */
-    @Binds
-    public abstract VolumeComponent provideVolumeComponent(
-            VolumeDialogComponent volumeDialogComponent);
-
-    /**
-     */
-    @Binds
-    public abstract RingerModeTracker provideRingerModeTracker(
-            RingerModeTrackerImpl ringerModeTrackerImpl);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 21bcfcd..0dd9488 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -18,6 +18,8 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.INotificationManager;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -26,6 +28,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.view.Choreographer;
 import android.view.IWindowManager;
@@ -39,6 +42,7 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.ModeSwitchesController;
 import com.android.systemui.accessibility.SystemActions;
@@ -51,6 +55,7 @@
 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;
@@ -60,9 +65,8 @@
 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.Divider;
+import com.android.systemui.stackdivider.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -79,7 +83,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 import dagger.Module;
@@ -89,15 +92,16 @@
  * Provides dependencies for the root component of sysui injection.
  *
  * Only SystemUI owned classes and instances should go in here. Other, framework-owned classes
- * should go in {@link SystemServicesModule}.
+ * should go in {@link FrameworkServicesModule}.
  *
  * See SystemUI/docs/dagger.md
  */
 @Module(includes = {NightDisplayListenerModule.class})
 public class DependencyProvider {
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     @Named(TIME_TICK_HANDLER_NAME)
     public Handler provideTimeTickHandler() {
         HandlerThread thread = new HandlerThread("TimeTick");
@@ -124,14 +128,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);
@@ -139,41 +145,54 @@
     }
 
     /** */
-    @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
+    @SuppressLint("MissingPermission")
+    @SysUISingleton
     @Provides
+    @Nullable
+    static LocalBluetoothManager provideLocalBluetoothController(Context context,
+            @Background Handler bgHandler) {
+        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
+    }
+
+    /** */
+    @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,
             WindowManager windowManager,
             Lazy<AssistManager> assistManagerLazy,
@@ -187,7 +206,7 @@
             SysUiState sysUiFlagsContainer,
             BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue,
-            Divider divider,
+            Optional<SplitScreen> splitScreenOptional,
             Optional<Recents> recentsOptional,
             Lazy<StatusBar> statusBarLazy,
             ShadeController shadeController,
@@ -209,7 +228,7 @@
                 sysUiFlagsContainer,
                 broadcastDispatcher,
                 commandQueue,
-                divider,
+                splitScreenOptional,
                 recentsOptional,
                 statusBarLazy,
                 shadeController,
@@ -220,29 +239,31 @@
                 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,
@@ -256,8 +277,9 @@
         return bD;
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() {
         return DevicePolicyManagerWrapper.getInstance();
     }
@@ -293,24 +315,23 @@
     }
 
     /** */
-    @Singleton
     @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/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 251ce13..66063a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dagger;
 
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
@@ -41,16 +40,16 @@
 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;
 import android.os.BatteryStats;
-import android.os.Handler;
 import android.os.PowerManager;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.service.dreams.DreamService;
@@ -58,6 +57,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.view.IWindowManager;
+import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
@@ -65,8 +65,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -80,7 +78,7 @@
  * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
  */
 @Module
-public class SystemServicesModule {
+public class FrameworkServicesModule {
     @Provides
     @Singleton
     static AccessibilityManager provideAccessibilityManager(Context context) {
@@ -93,8 +91,8 @@
         return context.getSystemService(ActivityManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static AlarmManager provideAlarmManager(Context context) {
         return context.getSystemService(AlarmManager.class);
     }
@@ -141,14 +139,14 @@
         return context.getSystemService(DisplayManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static IActivityManager provideIActivityManager() {
         return ActivityManager.getService();
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static IActivityTaskManager provideIActivityTaskManager() {
         return ActivityTaskManager.getService();
     }
@@ -169,12 +167,20 @@
 
     @Provides
     @Singleton
+    @Nullable
+    static FaceManager provideFaceManager(Context context) {
+        return context.getSystemService(FaceManager.class);
+
+    }
+
+    @Provides
+    @Singleton
     static IPackageManager provideIPackageManager() {
         return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static IStatusBarService provideIStatusBarService() {
         return IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -187,71 +193,67 @@
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static IWindowManager provideIWindowManager() {
         return WindowManagerGlobal.getWindowManagerService();
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static KeyguardManager provideKeyguardManager(Context context) {
         return context.getSystemService(KeyguardManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static LatencyTracker provideLatencyTracker(Context context) {
         return LatencyTracker.getInstance(context);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static LauncherApps provideLauncherApps(Context context) {
         return context.getSystemService(LauncherApps.class);
     }
 
-    @SuppressLint("MissingPermission")
-    @Singleton
-    @Provides
-    @Nullable
-    static LocalBluetoothManager provideLocalBluetoothController(Context context,
-            @Background Handler bgHandler) {
-        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
-    }
-
     @Provides
     static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
         return MediaRouter2Manager.getInstance(context);
     }
 
     @Provides
+    static MediaSessionManager provideMediaSessionManager(Context context) {
+        return context.getSystemService(MediaSessionManager.class);
+    }
+
+    @Provides
     @Singleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static NotificationManager provideNotificationManager(Context context) {
         return context.getSystemService(NotificationManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static PackageManager providePackageManager(Context context) {
         return context.getPackageManager();
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
 
     /** */
-    @Singleton
     @Provides
+    @Singleton
     static PowerManager providePowerManager(Context context) {
         return context.getSystemService(PowerManager.class);
     }
@@ -264,18 +266,24 @@
 
     @Provides
     @Singleton
+    static RoleManager provideRoleManager(Context context) {
+        return context.getSystemService(RoleManager.class);
+    }
+
+    @Provides
+    @Singleton
     static SensorManager providesSensorManager(Context context) {
         return context.getSystemService(SensorManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
         return context.getSystemService(SensorPrivacyManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static ShortcutManager provideShortcutManager(Context context) {
         return context.getSystemService(ShortcutManager.class);
     }
@@ -308,6 +316,12 @@
 
     @Provides
     @Singleton
+    static ViewConfiguration provideViewConfiguration(Context context) {
+        return ViewConfiguration.get(context);
+    }
+
+    @Provides
+    @Singleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
@@ -324,15 +338,9 @@
         return context.getSystemService(WifiManager.class);
     }
 
-    @Singleton
     @Provides
+    @Singleton
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
-
-    @Provides
-    @Singleton
-    static RoleManager provideRoleManager(Context context) {
-        return context.getSystemService(RoleManager.class);
-    }
 }
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..c5dc8cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.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.dagger;
+
+import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
+
+import dagger.Module;
+
+/**
+ * Supplies globally scoped instances that should be available in all versions of SystemUI
+ *
+ * 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 go 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(includes = {
+        FrameworkServicesModule.class,
+        GlobalConcurrencyModule.class})
+public class GlobalModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 9c55095..00fdf55 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -18,16 +18,7 @@
 
 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.keyguard.KeyguardSliceProvider;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
-import com.android.systemui.pip.phone.dagger.PipModule;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.concurrency.ThreadFactory;
 
 import javax.inject.Singleton;
 
@@ -37,18 +28,10 @@
 /**
  * Root component for Dagger injection.
  */
-// TODO(b/162923491): Move most of these modules to SysUIComponent.
 @Singleton
 @Component(modules = {
-        DefaultComponentBinder.class,
-        DependencyProvider.class,
-        DependencyBinder.class,
-        OneHandedModule.class,
-        PipModule.class,
-        SystemServicesModule.class,
-        SystemUIBinder.class,
-        SystemUIModule.class,
-        SystemUIDefaultModule.class,
+        GlobalModule.class,
+        SysUISubcomponentModule.class,
         WMModule.class})
 public interface GlobalRootComponent {
 
@@ -74,51 +57,7 @@
     SysUIComponent.Builder getSysUIComponent();
 
     /**
-     * Provides a BootCompleteCache.
+     * Build a {@link ThreadFactory}.
      */
-    @Singleton
-    BootCompleteCacheImpl provideBootCacheImpl();
-
-    /**
-     * Creates a ContextComponentHelper.
-     */
-    @Singleton
-    ConfigurationController getConfigurationController();
-
-    /**
-     * Creates a ContextComponentHelper.
-     */
-    @Singleton
-    ContextComponentHelper getContextComponentHelper();
-
-    /**
-     * Main dependency providing module.
-     */
-    @Singleton
-    Dependency createDependency();
-
-    /** */
-    @Singleton
-    DumpManager createDumpManager();
-
-    /**
-     * Creates a InitController.
-     */
-    @Singleton
-    InitController getInitController();
-
-    /**
-     * ViewInstanceCreator generates all Views that need injection.
-     */
-    InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
-
-    /**
-     * Member injection into the supplied argument.
-     */
-    void inject(SystemUIAppComponentFactory factory);
-
-    /**
-     * Member injection into the supplied argument.
-     */
-    void inject(KeyguardSliceProvider keyguardSliceProvider);
+    ThreadFactory createThreadFactory();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
new file mode 100644
index 0000000..406981d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 com.android.systemui.ActivityStarterDelegate;
+import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Module for binding Plugin implementations.
+ *
+ * TODO(b/166258224): Many of these should be moved closer to their implementations.
+ */
+@Module
+public interface PluginModule {
+
+    /** */
+    @Binds
+    ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate);
+
+    /** */
+    @Binds
+    DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
+
+    /** */
+    @Binds
+    FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+
+    /** */
+    @Binds
+    GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+    /** */
+    @Binds
+    GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+            GlobalActionsComponent controllerImpl);
+
+    /** */
+    @Binds
+    StatusBarStateController provideStatusBarStateController(
+            StatusBarStateControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    VolumeDialogController provideVolumeDialogController(VolumeDialogControllerImpl controllerImpl);
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 9b527bf..2622593 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,13 +16,30 @@
 
 package com.android.systemui.dagger;
 
+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.keyguard.KeyguardSliceProvider;
+import com.android.systemui.pip.phone.dagger.PipModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.InjectionInflationController;
+
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Dagger Subcomponent for Core SysUI.
  */
 @SysUISingleton
-@Subcomponent(modules = {})
+@Subcomponent(modules = {
+        DefaultComponentBinder.class,
+        DependencyProvider.class,
+        PipModule.class,
+        SystemUIBinder.class,
+        SystemUIModule.class,
+        SystemUIDefaultModule.class})
 public interface SysUIComponent {
 
     /**
@@ -30,6 +47,58 @@
      */
     @Subcomponent.Builder
     interface Builder {
+        @BindsInstance
+        Builder setStubAPIClass(WMComponent.StubAPIClass stubAPIClass);
+
         SysUIComponent build();
     }
+
+    /**
+     * Provides a BootCompleteCache.
+     */
+    @SysUISingleton
+    BootCompleteCacheImpl provideBootCacheImpl();
+
+    /**
+     * Creates a ContextComponentHelper.
+     */
+    @SysUISingleton
+    ConfigurationController getConfigurationController();
+
+    /**
+     * Creates a ContextComponentHelper.
+     */
+    @SysUISingleton
+    ContextComponentHelper getContextComponentHelper();
+
+    /**
+     * Main dependency providing module.
+     */
+    @SysUISingleton
+    Dependency createDependency();
+
+    /** */
+    @SysUISingleton
+    DumpManager createDumpManager();
+
+    /**
+     * Creates a InitController.
+     */
+    @SysUISingleton
+    InitController getInitController();
+
+    /**
+     * ViewInstanceCreator generates all Views that need injection.
+     */
+    InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(SystemUIAppComponentFactory factory);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(KeyguardSliceProvider keyguardSliceProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
similarity index 74%
copy from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
index 9d05843..aacc693 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings
+package com.android.systemui.dagger;
 
-import android.content.ContentResolver
+import dagger.Module;
 
-interface CurrentUserContentResolverProvider {
-
-    val currentUserContentResolver: ContentResolver
-}
\ No newline at end of file
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {SysUIComponent.class})
+public abstract class SysUISubcomponentModule {
+}
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 b4e6e0e..a021114 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -41,7 +41,6 @@
 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;
@@ -62,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;
@@ -76,16 +74,12 @@
  * overridden by the System UI implementation.
  */
 @Module(includes = {
-            DividerModule.class,
             QSModule.class,
-            WindowManagerShellModule.class
-        },
-        subcomponents = {
-            SysUIComponent.class
+            WMShellModule.class
         })
 public abstract class SystemUIDefaultModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     @Nullable
@@ -101,7 +95,7 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static BatteryController provideBatteryController(
             Context context,
             EnhancedEstimates enhancedEstimates,
@@ -123,7 +117,7 @@
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -136,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,
@@ -159,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);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a46e182..99a3a91 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -16,24 +16,20 @@
 
 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.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
+import com.android.systemui.controls.dagger.ControlsModule;
 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.power.dagger.PowerModule;
 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;
@@ -41,18 +37,18 @@
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.KeyguardLiftController;
 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.statusbar.policy.dagger.StatusBarPolicyModule;
+import com.android.systemui.tuner.dagger.TunerModule;
+import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
+import com.android.systemui.util.dagger.UtilModule;
 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 com.android.systemui.volume.dagger.VolumeModule;
 
 import dagger.Binds;
 import dagger.BindsOptionalOf;
@@ -64,14 +60,23 @@
  * implementation.
  */
 @Module(includes = {
+            AppOpsModule.class,
             AssistModule.class,
-            ConcurrencyModule.class,
+            ControlsModule.class,
             DemoModeModule.class,
             LogModule.class,
             PeopleHubModule.class,
+            PowerModule.class,
+            PluginModule.class,
+            ScreenshotModule.class,
             SensorModule.class,
             SettingsModule.class,
-            SettingsUtilModule.class
+            SettingsUtilModule.class,
+            StatusBarPolicyModule.class,
+            SysUIConcurrencyModule.class,
+            TunerModule.class,
+            UtilModule.class,
+            VolumeModule.class
         },
         subcomponents = {StatusBarComponent.class,
                 NotificationRowComponent.class,
@@ -89,28 +94,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();
@@ -120,9 +109,6 @@
     abstract CommandQueue optionalCommandQueue();
 
     @BindsOptionalOf
-    abstract Divider optionalDivider();
-
-    @BindsOptionalOf
     abstract HeadsUpManager optionalHeadsUpManager();
 
     @BindsOptionalOf
@@ -131,7 +117,7 @@
     @BindsOptionalOf
     abstract StatusBar optionalStatusBar();
 
-    @Singleton
+    @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 929b61a..ad90eff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dagger;
 
+import javax.inject.Inject;
+
 import dagger.Subcomponent;
 
 /**
@@ -32,4 +34,19 @@
     interface Builder {
         WMComponent build();
     }
+
+
+    /**
+     *  Example class used for passing an API to SysUI from WMShell.
+     *
+     *  TODO: Remove this once real WM classes are ready to go.
+     **/
+    @WMSingleton
+    class StubAPIClass {
+        @Inject
+        StubAPIClass() {}
+    }
+
+    /** Create a StubAPIClass. */
+    StubAPIClass createStubAPIClass();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
index 9270b88..de9affb 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -18,12 +18,11 @@
 
 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 javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -34,7 +33,7 @@
 public abstract class DemoModeModule {
     /** Provides DemoModeController */
     @Provides
-    @Singleton
+    @SysUISingleton
     static DemoModeController provideDemoModeController(
             Context context,
             DumpManager dumpManager,
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/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index e38dce0..8364b48 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -262,11 +262,11 @@
             onWakeScreen(wakeEvent, mMachine.isExecutingTransition() ? null : mMachine.getState());
         } else if (isLongPress) {
             requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
-                    null /* onPulseSupressedListener */);
+                    null /* onPulseSuppressedListener */);
         } else if (isWakeLockScreen) {
             if (wakeEvent) {
                 requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
-                        null /* onPulseSupressedListener */);
+                        null /* onPulseSuppressedListener */);
             }
         } else {
             proximityCheckThenCall((result) -> {
@@ -536,7 +536,7 @@
             if (PULSE_ACTION.equals(intent.getAction())) {
                 if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent");
                 requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */
-                        null /* onPulseSupressedListener */);
+                        null /* onPulseSuppressedListener */);
             }
             if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
                 mMachine.requestState(DozeMachine.State.FINISH);
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 e888869..0673879 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -21,6 +21,7 @@
 import android.view.View;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -30,7 +31,6 @@
 import java.lang.reflect.Modifier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Subcomponent;
 
@@ -38,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";
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 d213ac1..3e64749 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -130,7 +130,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -252,7 +252,7 @@
     private final RingerModeTracker mRingerModeTracker;
     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
     private Handler mMainHandler;
-    private CurrentUserContextTracker mCurrentUserContextTracker;
+    private UserContextProvider mUserContextProvider;
     @VisibleForTesting
     boolean mShowLockScreenCardsAndControls = false;
 
@@ -313,7 +313,7 @@
             UiEventLogger uiEventLogger,
             RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
             ControlsComponent controlsComponent,
-            CurrentUserContextTracker currentUserContextTracker) {
+            UserContextProvider userContextProvider) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -342,7 +342,7 @@
         mControlsControllerOptional = controlsComponent.getControlsController();
         mSysUiState = sysUiState;
         mMainHandler = handler;
-        mCurrentUserContextTracker = currentUserContextTracker;
+        mUserContextProvider = userContextProvider;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -436,7 +436,7 @@
         String[] preferredControlsPackages = mContext.getResources()
                 .getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages);
 
-        SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext()
+        SharedPreferences prefs = mUserContextProvider.getUserContext()
                 .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
         Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
                 Collections.emptySet());
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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6214a64..3340791 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -89,15 +89,14 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 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.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.InjectionInflationController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -228,7 +227,6 @@
 
     /** 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
@@ -345,7 +343,7 @@
     /**
      * For managing external displays
      */
-    private KeyguardDisplayManager mKeyguardDisplayManager;
+    private final KeyguardDisplayManager mKeyguardDisplayManager;
 
     private final ArrayList<IKeyguardStateCallback> mKeyguardStateCallbacks = new ArrayList<>();
 
@@ -724,7 +722,7 @@
             TrustManager trustManager,
             DeviceConfigProxy deviceConfig,
             NavigationModeController navigationModeController,
-            InjectionInflationController injectionInflationController) {
+            KeyguardDisplayManager keyguardDisplayManager) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -735,7 +733,7 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mPM = powerManager;
         mTrustManager = trustManager;
-        mInjectionInflationController = injectionInflationController;
+        mKeyguardDisplayManager = keyguardDisplayManager;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -775,9 +773,6 @@
         mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
                 SYSTEMUI_PERMISSION, null /* scheduler */);
 
-        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext,
-                mInjectionInflationController);
-
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
         KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
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 fc789a6..9d8e73a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -16,28 +16,42 @@
 
 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.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 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.plugins.FalsingManager;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.FalsingManager;
+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;
@@ -45,13 +59,13 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module
+@Module(subcomponents = {KeyguardStatusViewComponent.class})
 public class KeyguardModule {
     /**
      * Provides our instance of KeyguardViewMediator which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
             FalsingManager falsingManager,
@@ -66,7 +80,7 @@
             @UiBackground Executor uiBgExecutor,
             DeviceConfigProxy deviceConfig,
             NavigationModeController navigationModeController,
-            InjectionInflationController injectionInflationController) {
+            KeyguardDisplayManager keyguardDisplayManager) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -81,6 +95,53 @@
                 trustManager,
                 deviceConfig,
                 navigationModeController,
-                injectionInflationController);
+                keyguardDisplayManager
+        );
+    }
+
+    @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 b993faa..e5a9ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -12,11 +12,12 @@
 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
@@ -25,7 +26,6 @@
 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)
@@ -34,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>,
@@ -42,7 +42,7 @@
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val activityStarter: ActivityStarter,
     @Main executor: DelayableExecutor,
-    mediaManager: MediaDataFilter,
+    mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager
 ) {
@@ -155,6 +155,7 @@
         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
@@ -171,7 +172,6 @@
                     // 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 {
                     addOrUpdatePlayer(key, oldKey, data)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 77cac50..4863999 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.util.animation.PhysicsAnimator
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -315,7 +316,8 @@
         return false
     }
 
-    private fun isFalseTouch() = falsingProtectionNeeded && falsingManager.isFalseTouch
+    private fun isFalseTouch() = falsingProtectionNeeded &&
+            falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
 
     private fun getMaxTranslation() = if (showsSettingsButton) {
             settingsButton.width
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 bff334e..686531a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -42,6 +42,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
@@ -56,9 +57,6 @@
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
-import kotlin.collections.ArrayList
-import kotlin.collections.LinkedHashMap
 
 // URI fields to try loading album art from
 private val ART_URIS = arrayOf(
@@ -91,7 +89,7 @@
 /**
  * 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,
@@ -101,12 +99,23 @@
     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) {
@@ -130,9 +139,14 @@
         broadcastDispatcher: BroadcastDispatcher,
         mediaTimeoutListener: MediaTimeoutListener,
         mediaResumeListener: MediaResumeListener,
+        mediaSessionBasedFilter: MediaSessionBasedFilter,
+        mediaDeviceManager: MediaDeviceManager,
+        mediaDataCombineLatest: MediaDataCombineLatest,
+        mediaDataFilter: MediaDataFilter,
         activityStarter: ActivityStarter
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
+            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
 
     private val appChangeReceiver = object : BroadcastReceiver() {
@@ -155,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)
@@ -198,10 +226,9 @@
 
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
-        val listenersCopy = listeners.toSet()
         val toRemove = mediaEntries.filter { it.value.packageName == packageName }
         toRemove.forEach {
-            removeEntry(it.key, listenersCopy)
+            removeEntry(it.key)
         }
     }
 
@@ -261,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.
@@ -284,16 +344,13 @@
         }
     }
 
-    private fun removeEntry(key: String, listenersCopy: Set<Listener>) {
+    private fun removeEntry(key: String) {
         mediaEntries.remove(key)
-        listenersCopy.forEach {
-            it.onMediaDataRemoved(key)
-        }
+        notifyMediaDataRemoved(key)
     }
 
     fun dismissMediaData(key: String, delay: Long) {
-        val listenersCopy = listeners.toSet()
-        foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay)
+        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
     }
 
     private fun loadMediaDataInBgForResumption(
@@ -422,7 +479,7 @@
                 val runnable = if (action.actionIntent != null) {
                     Runnable {
                         if (action.isAuthenticationRequired()) {
-                            activityStarter.dismissKeyguardThenExecute ({
+                            activityStarter.dismissKeyguardThenExecute({
                                 var result = sendPendingIntent(action.actionIntent)
                                 result
                             }, {}, true)
@@ -550,10 +607,7 @@
         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)
         }
     }
 
@@ -570,31 +624,21 @@
             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)
         }
     }
 
@@ -614,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 {
 
         /**
@@ -644,7 +702,8 @@
 
     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 70f01d5..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,
@@ -389,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() ||
@@ -604,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 c41712c..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,
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/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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 6c8a23b..c7e7817 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -111,7 +111,6 @@
 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.model.SysUiState;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
@@ -123,7 +122,7 @@
 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.SplitScreen;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -180,7 +179,7 @@
     private final NavigationModeController mNavigationModeController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final CommandQueue mCommandQueue;
-    private final Divider mDivider;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<Recents> mRecentsOptional;
     private final SystemActions mSystemActions;
     private final Handler mHandler;
@@ -377,22 +376,22 @@
 
     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);
+                    }
+                }
+            };
 
     private final DeviceProvisionedController.DeviceProvisionedListener mUserSetupListener =
             new DeviceProvisionedController.DeviceProvisionedListener() {
-        @Override
-        public void onUserSetupChanged() {
-            mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
-        }
-    };
+                @Override
+                public void onUserSetupChanged() {
+                    mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+                }
+            };
 
     public NavigationBar(Context context,
             WindowManager windowManager,
@@ -406,7 +405,8 @@
             StatusBarStateController statusBarStateController,
             SysUiState sysUiFlagsContainer,
             BroadcastDispatcher broadcastDispatcher,
-            CommandQueue commandQueue, Divider divider,
+            CommandQueue commandQueue,
+            Optional<SplitScreen> splitScreenOptional,
             Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
             ShadeController shadeController,
             NotificationRemoteInputManager notificationRemoteInputManager,
@@ -430,7 +430,7 @@
         mNavBarMode = navigationModeController.addListener(this);
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
-        mDivider = divider;
+        mSplitScreenOptional = splitScreenOptional;
         mRecentsOptional = recentsOptional;
         mSystemActions = systemActions;
         mHandler = mainHandler;
@@ -458,10 +458,10 @@
         lp.windowAnimations = 0;
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
 
-        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        NavigationBarFrame frame = (NavigationBarFrame) layoutInflater.inflate(
+        NavigationBarFrame frame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
                 R.layout.navigation_bar_window, null);
-        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
+        View barView = LayoutInflater.from(frame.getContext()).inflate(
+                R.layout.navigation_bar, frame);
         barView.addOnAttachStateChangeListener(this);
 
         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
@@ -528,6 +528,7 @@
         }
         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+        mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
 
         prepareNavigationBarView();
         checkNavBarModes();
@@ -559,7 +560,7 @@
         setDisabled2Flags(mDisabledFlags2);
         if (mIsOnDefaultDisplay) {
             mAssistHandlerViewController =
-                new AssistHandleViewController(mHandler, mNavigationBarView);
+                    new AssistHandleViewController(mHandler, mNavigationBarView);
             getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController);
         }
 
@@ -661,7 +662,7 @@
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
@@ -690,7 +691,8 @@
             return;
         }
 
-        if (mStartingQuickSwitchRotation == -1 || mDivider.isDividerVisible()) {
+        if (mStartingQuickSwitchRotation == -1 || mSplitScreenOptional
+                .map(SplitScreen::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();
@@ -1121,7 +1123,7 @@
         }
         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);
         mAssistManagerLazy.get().startAssist(args);
@@ -1246,10 +1248,12 @@
 
     private boolean onLongPressRecents() {
         if (mRecentsOptional.isPresent() || !ActivityTaskManager.supportsMultiWindow(mContext)
-                || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
                 || ActivityManager.isLowRamDeviceStatic()
                 // If we are connected to the overview service, then disable the recents button
-                || mOverviewProxyService.getProxy() != null) {
+                || mOverviewProxyService.getProxy() != null
+                || !mSplitScreenOptional.map(splitScreen ->
+                splitScreen.getDividerView().getSnapAlgorithm().isSplitScreenFeasible())
+                .orElse(false)) {
             return false;
         }
 
@@ -1311,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) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 73cdde8..9b9dc6d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -48,13 +48,14 @@
 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.Divider;
+import com.android.systemui.stackdivider.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -70,13 +71,12 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 
 /** A controller to handle navigation bars. */
-@Singleton
+@SysUISingleton
 public class NavigationBarController implements Callbacks,
         ConfigurationController.ConfigurationListener,
         NavigationModeController.ModeChangedListener, Dumpable {
@@ -96,7 +96,7 @@
     private final SysUiState mSysUiFlagsContainer;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final CommandQueue mCommandQueue;
-    private final Divider mDivider;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<Recents> mRecentsOptional;
     private final Lazy<StatusBar> mStatusBarLazy;
     private final ShadeController mShadeController;
@@ -130,7 +130,7 @@
             SysUiState sysUiFlagsContainer,
             BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue,
-            Divider divider,
+            Optional<SplitScreen> splitScreenOptional,
             Optional<Recents> recentsOptional,
             Lazy<StatusBar> statusBarLazy,
             ShadeController shadeController,
@@ -152,7 +152,7 @@
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
-        mDivider = divider;
+        mSplitScreenOptional = splitScreenOptional;
         mRecentsOptional = recentsOptional;
         mStatusBarLazy = statusBarLazy;
         mShadeController = shadeController;
@@ -278,7 +278,7 @@
                 mSysUiFlagsContainer,
                 mBroadcastDispatcher,
                 mCommandQueue,
-                mDivider,
+                mSplitScreenOptional,
                 mRecentsOptional,
                 mStatusBarLazy,
                 mShadeController,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index a335183..8a468f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -87,14 +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.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 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;
 
@@ -329,8 +328,8 @@
         mContextualButtonGroup.addButton(accessibilityButton);
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
         mFloatingRotationButton = new FloatingRotationButton(context);
+        mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
         mRotationButtonController = new RotationButtonController(mLightContext,
                 mLightIconColor, mDarkIconColor,
                 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
@@ -912,9 +911,6 @@
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
-
-        Divider divider = Dependency.get(Divider.class);
-        divider.registerInSplitScreenListener(mDockedListener);
         updateOrientationViews();
         reloadNavIcons();
     }
@@ -1287,6 +1283,10 @@
         return super.onApplyWindowInsets(insets);
     }
 
+    void registerDockedListener(SplitScreen splitScreen) {
+        splitScreen.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/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 6e301c9..c704d32 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -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/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 85%
rename from packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
rename to packages/SystemUI/src/com/android/systemui/onehanded/OneHandedController.java
index 42524bb..bb59449 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedController.java
@@ -31,7 +31,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+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;
@@ -42,13 +44,12 @@
 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";
@@ -94,7 +95,6 @@
     /**
      * Handle rotation based on OnDisplayChangingListener callback
      */
-    @VisibleForTesting
     private final DisplayChangeController.OnDisplayChangingListener mRotationController =
             (display, fromRotation, toRotation, wct) -> {
                 if (mDisplayAreaOrganizer != null) {
@@ -106,7 +106,38 @@
      * 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,
@@ -163,7 +194,6 @@
     /**
      * Enters one handed mode.
      */
-    @Override
     public void startOneHanded() {
         if (!mDisplayAreaOrganizer.isInOneHanded()) {
             final int yOffSet = Math.round(getDisplaySize().y * mOffSetFraction);
@@ -175,7 +205,6 @@
     /**
      * Exits one handed mode.
      */
-    @Override
     public void stopOneHanded() {
         if (mDisplayAreaOrganizer.isInOneHanded()) {
             mDisplayAreaOrganizer.scheduleOffset(0, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
index 2e91697..ad9f7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -48,8 +48,6 @@
 import java.util.List;
 import java.util.Objects;
 
-import javax.inject.Inject;
-
 /**
  * Manages OneHanded display areas such as offset.
  *
@@ -149,7 +147,6 @@
     /**
      * Constructor of OneHandedDisplayAreaOrganizer
      */
-    @Inject
     public OneHandedDisplayAreaOrganizer(Context context,
             DisplayController displayController,
             OneHandedAnimationController animationController,
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
index ba50db1..f3be699 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
@@ -44,14 +44,10 @@
 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
      */
@@ -269,7 +264,7 @@
     }
 
     /**
-     * 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 {
         /**
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 279bbc8..0598f32 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
@@ -22,6 +22,8 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -29,6 +31,7 @@
 /**
  * APIs for querying or updating one handed settings .
  */
+@SysUISingleton
 public final class OneHandedSettingsUtil {
     private static final String TAG = "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 1446e5a..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,7 +166,7 @@
     }
 
     /**
-     * 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 {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
index de34b2e..8ef9b09 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
@@ -39,16 +39,12 @@
 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 =
@@ -76,7 +72,6 @@
         }
     };
 
-    @Inject
     public OneHandedTutorialHandler(Context context) {
         context.getDisplay().getRealSize(mDisplaySize);
         mContentResolver = context.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
index 2767a8d..3348a06 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
@@ -40,6 +40,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -47,19 +48,18 @@
 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;
@@ -74,8 +74,8 @@
             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);
             }
 
             // Also checks swipe to notification settings since they all need gesture overlay.
@@ -125,8 +125,8 @@
                     ? 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);
             }
         }
     };
@@ -138,8 +138,8 @@
                     final boolean enabled =
                             OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
                                     mContext.getContentResolver());
-                    if (mOneHandedManager != null) {
-                        mOneHandedManager.setSwipeToNotificationEnabled(enabled);
+                    if (mOneHandedController != null) {
+                        mOneHandedController.setSwipeToNotificationEnabled(enabled);
                     }
 
                     // Also checks one handed mode settings since they all need gesture overlay.
@@ -152,14 +152,14 @@
     @Inject
     public OneHandedUI(Context context,
             CommandQueue commandQueue,
-            OneHandedManagerImpl oneHandedManager,
+            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;
             mTimeoutHandler = null;
             mScreenLifecycle = null;
@@ -167,7 +167,7 @@
         }
 
         mCommandQueue = commandQueue;
-        mOneHandedManager = oneHandedManager;
+        mOneHandedController = oneHandedController;
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         mScreenLifecycle = screenLifecycle;
         mOverlayManager = IOverlayManager.Stub.asInterface(
@@ -260,13 +260,13 @@
     }
 
     private void updateSettings() {
-        mOneHandedManager.setOneHandedEnabled(OneHandedSettingsUtil
+        mOneHandedController.setOneHandedEnabled(OneHandedSettingsUtil
                 .getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
         mTimeoutHandler.setTimeout(OneHandedSettingsUtil
                 .getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
-        mOneHandedManager.setTaskChangeToExit(OneHandedSettingsUtil
+        mOneHandedController.setTaskChangeToExit(OneHandedSettingsUtil
                 .getSettingsTapsAppToExit(mContext.getContentResolver()));
-        mOneHandedManager.setSwipeToNotificationEnabled(OneHandedSettingsUtil
+        mOneHandedController.setSwipeToNotificationEnabled(OneHandedSettingsUtil
                 .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
     }
 
@@ -295,7 +295,7 @@
      * Trigger one handed more
      */
     public void startOneHanded() {
-        mOneHandedManager.startOneHanded();
+        mOneHandedController.startOneHanded();
         OneHandedEvents.writeEvent(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
     }
 
@@ -303,7 +303,7 @@
      * Dismiss one handed more
      */
     public void stopOneHanded() {
-        mOneHandedManager.stopOneHanded();
+        mOneHandedController.stopOneHanded();
         OneHandedEvents.writeEvent(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
     }
 
@@ -314,8 +314,8 @@
         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) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 4931388..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).
  */
@@ -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 025341c..c956702 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -49,6 +49,9 @@
 import android.util.Log;
 import android.util.Size;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -56,9 +59,12 @@
 import android.window.WindowOrganizer;
 
 import com.android.internal.os.SomeArgs;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.pip.phone.PipMenuActivityController;
 import com.android.systemui.pip.phone.PipUpdateThread;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreen;
 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 +73,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 +87,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;
@@ -95,6 +99,7 @@
     private static final int MSG_FINISH_RESIZE = 4;
     private static final int MSG_RESIZE_USER = 5;
 
+    private final Context mContext;
     private final Handler mMainHandler;
     private final Handler mUpdateHandler;
     private final PipBoundsHandler mPipBoundsHandler;
@@ -105,7 +110,10 @@
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
-    private final Divider mSplitDivider;
+    private final Optional<SplitScreen> mSplitScreenOptional;
+    protected final ShellTaskOrganizer mTaskOrganizer;
+    private SurfaceControlViewHost mPipViewHost;
+    private SurfaceControl mPipMenuSurface;
 
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -205,23 +213,25 @@
      */
     private boolean mShouldDeferEnteringPip;
 
-    @Inject
     public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
-            @Nullable Divider divider,
+            Optional<SplitScreen> splitScreenOptional,
             @NonNull DisplayController displayController,
-            @NonNull PipAnimationController pipAnimationController,
-            @NonNull PipUiEventLogger pipUiEventLogger) {
+            @NonNull PipUiEventLogger pipUiEventLogger,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+        mContext = context;
         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;
+        mSplitScreenOptional = splitScreenOptional;
+        mTaskOrganizer = shellTaskOrganizer;
+        mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED);
         displayController.addDisplayWindowListener(this);
     }
 
@@ -316,7 +326,7 @@
                     : 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();
@@ -335,9 +345,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_LEAVE_PIP_TO_SPLIT_SCREEN) {
-            wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
-        }
+        mSplitScreenOptional.ifPresent(splitScreen -> {
+            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+                wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
+            }
+        });
     }
 
     /**
@@ -446,7 +458,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();
@@ -500,6 +512,45 @@
     }
 
     /**
+     * Setup the ViewHost and attach the provided menu view to the ViewHost.
+     */
+    public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
+        if (mPipMenuSurface != null) {
+            Log.e(TAG, "PIP Menu View already created and attached.");
+            return;
+        }
+
+        if (Looper.getMainLooper() != Looper.myLooper()) {
+            throw new RuntimeException("PipMenuView needs to be attached on the main thread.");
+        }
+
+        mPipViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+                (android.os.Binder) null);
+        mPipMenuSurface = mPipViewHost.getSurfacePackage().getSurfaceControl();
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        transaction.reparent(mPipMenuSurface, mLeash);
+        transaction.show(mPipMenuSurface);
+        transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1);
+        transaction.apply();
+        mPipViewHost.setView(menuView, lp);
+    }
+
+
+    /**
+     * Releases the PIP Menu's View host, remove it from PIP task surface.
+     */
+    public void detachPipMenuViewHost() {
+        if (mPipMenuSurface != null) {
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            transaction.remove(mPipMenuSurface);
+            transaction.apply();
+            mPipMenuSurface = null;
+            mPipViewHost = null;
+        }
+    }
+
+
+    /**
      * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
      * Meanwhile this callback is invoked whenever the task is removed. For instance:
      *   - as a result of removeStacksInWindowingModes from WM
@@ -543,11 +594,6 @@
     }
 
     @Override
-    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        // Do nothing
-    }
-
-    @Override
     public void onFixedRotationStarted(int displayId, int newRotation) {
         mShouldDeferEnteringPip = true;
     }
@@ -839,6 +885,12 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareFinishResizeTransaction(destinationBounds, direction, tx, wct);
         applyFinishBoundsResize(wct, direction);
+        runOnMainHandler(() -> {
+            if (mPipViewHost != null) {
+                mPipViewHost.relayout(PipMenuActivityController.getPipMenuLayoutParams(
+                        destinationBounds.width(), destinationBounds.height()));
+            }
+        });
     }
 
     private void prepareFinishResizeTransaction(Rect destinationBounds,
@@ -938,20 +990,26 @@
     }
 
     /**
-     * Sync with {@link #mSplitDivider} on destination bounds if PiP is going to split screen.
+     * Sync with {@link SplitScreen} 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 (!mSplitScreenOptional.isPresent()) {
             return false;
         }
+
+        SplitScreen splitScreen = mSplitScreenOptional.get();
+        if (!splitScreen.isDividerVisible()) {
+            // 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(splitScreen.getDividerView()
+                .getNonMinimizedSplitScreenSecondaryBounds());
         return true;
     }
 
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
index 5e2cd9c..8bcaa8a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
@@ -20,22 +20,18 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
+import com.android.systemui.dagger.SysUISingleton;
 
 /**
  * Helper class that ends PiP log to UiEvent, see also go/uievent
  */
-@Singleton
+@SysUISingleton
 public class PipUiEventLogger {
 
     private final UiEventLogger mUiEventLogger;
 
     private TaskInfo mTaskInfo;
 
-    @Inject
     public PipUiEventLogger(UiEventLogger uiEventLogger) {
         mUiEventLogger = uiEventLogger;
     }
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 9dfa864..5ef5b90 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -42,34 +42,36 @@
 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.SplitScreen;
 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";
 
@@ -82,15 +84,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;
 
     /**
@@ -101,15 +105,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
@@ -135,16 +140,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.
@@ -228,12 +239,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();
+            });
         }
     }
 
@@ -252,16 +266,14 @@
 
     @Inject
     public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
-            @PipMenuActivityClass Class<?> pipMenuActivityClass,
-            DisplayController displayController,
-            FloatingContentCoordinator floatingContentCoordinator,
-            DeviceConfigProxy deviceConfig,
-            PipBoundsHandler pipBoundsHandler,
-            PipSnapAlgorithm pipSnapAlgorithm,
-            PipTaskOrganizer pipTaskOrganizer,
-            SysUiState sysUiState,
             ConfigurationController configController,
-            PipUiEventLogger pipUiEventLogger) {
+            DeviceConfigProxy deviceConfig,
+            DisplayController displayController,
+            Optional<SplitScreen> splitScreenOptional,
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState,
+            PipUiEventLogger pipUiEventLogger,
+            ShellTaskOrganizer shellTaskOrganizer) {
         mContext = context;
         mActivityManager = ActivityManager.getService();
 
@@ -273,17 +285,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, splitScreenOptional, mDisplayController,
+                pipUiEventLogger, shellTaskOrganizer);
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
-        mMenuController = new PipMenuActivityController(context, pipMenuActivityClass,
-                mMediaController, mInputConsumerController);
+        mMenuController = new PipMenuActivityController(context,
+                mMediaController, mInputConsumerController, mPipTaskOrganizer);
         mTouchHandler = new PipTouchHandler(context, mActivityManager,
                 mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
-                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm, sysUiState,
-                pipUiEventLogger);
+                floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
         displayController.addDisplayChangingController(mRotationController);
@@ -298,7 +313,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) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
deleted file mode 100644
index 1b1b2de..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ /dev/null
@@ -1,730 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU_WITH_DELAY;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_RESIZE_HANDLE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.wm.shell.R;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * 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 {
-
-    private static final String TAG = "PipMenuActivity";
-
-    private static final int MESSAGE_INVALID_TYPE = -1;
-
-    public static final int MESSAGE_SHOW_MENU = 1;
-    public static final int MESSAGE_POKE_MENU = 2;
-    public static final int MESSAGE_HIDE_MENU = 3;
-    public static final int MESSAGE_UPDATE_ACTIONS = 4;
-    public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
-    public static final int MESSAGE_ANIMATION_ENDED = 6;
-    public static final int MESSAGE_POINTER_EVENT = 7;
-    public static final int MESSAGE_MENU_EXPANDED = 8;
-    public static final int MESSAGE_FADE_OUT_MENU = 9;
-    public static final int MESSAGE_UPDATE_MENU_LAYOUT = 10;
-
-    private static final int INITIAL_DISMISS_DELAY = 3500;
-    private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
-    private static final long MENU_FADE_DURATION = 125;
-    private static final long MENU_SLOW_FADE_DURATION = 175;
-    private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
-
-    private static final float MENU_BACKGROUND_ALPHA = 0.3f;
-    private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
-
-    private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
-    private static final boolean ENABLE_RESIZE_HANDLE = false;
-
-    private int mMenuState;
-    private boolean mResize = true;
-    private boolean mAllowMenuTimeout = true;
-    private boolean mAllowTouches = true;
-
-    private final List<RemoteAction> mActions = new ArrayList<>();
-
-    private AccessibilityManager mAccessibilityManager;
-    private Drawable mBackgroundDrawable;
-    private View mMenuContainer;
-    private LinearLayout mActionsGroup;
-    private int mBetweenActionPaddingLand;
-
-    private AnimatorSet mMenuContainerAnimator;
-
-    private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    final float alpha = (float) animation.getAnimatedValue();
-                    mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255));
-                }
-            };
-
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_SHOW_MENU: {
-                    final Bundle data = (Bundle) msg.obj;
-                    showMenu(data.getInt(EXTRA_MENU_STATE),
-                            data.getParcelable(EXTRA_STACK_BOUNDS),
-                            data.getBoolean(EXTRA_ALLOW_TIMEOUT),
-                            data.getBoolean(EXTRA_WILL_RESIZE_MENU),
-                            data.getBoolean(EXTRA_SHOW_MENU_WITH_DELAY),
-                            data.getBoolean(EXTRA_SHOW_RESIZE_HANDLE));
-                    break;
-                }
-                case MESSAGE_POKE_MENU:
-                    cancelDelayedFinish();
-                    break;
-                case MESSAGE_HIDE_MENU:
-                    hideMenu((Runnable) msg.obj);
-                    break;
-                case MESSAGE_UPDATE_ACTIONS: {
-                    final Bundle data = (Bundle) msg.obj;
-                    final ParceledListSlice<RemoteAction> actions = data.getParcelable(
-                            EXTRA_ACTIONS);
-                    setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null
-                            ? actions.getList() : Collections.emptyList());
-                    break;
-                }
-                case MESSAGE_UPDATE_DISMISS_FRACTION: {
-                    final Bundle data = (Bundle) msg.obj;
-                    updateDismissFraction(data.getFloat(EXTRA_DISMISS_FRACTION));
-                    break;
-                }
-                case MESSAGE_ANIMATION_ENDED: {
-                    mAllowTouches = true;
-                    break;
-                }
-                case MESSAGE_POINTER_EVENT: {
-                    final MotionEvent ev = (MotionEvent) msg.obj;
-                    dispatchPointerEvent(ev);
-                    break;
-                }
-                case MESSAGE_MENU_EXPANDED : {
-                    if (mMenuContainerAnimator == null) {
-                        return;
-                    }
-                    mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
-                    mMenuContainerAnimator.start();
-                    break;
-                }
-                case MESSAGE_FADE_OUT_MENU: {
-                    fadeOutMenu();
-                    break;
-                }
-                case MESSAGE_UPDATE_MENU_LAYOUT: {
-                    if (mPipMenuIconsAlgorithm == null) {
-                        return;
-                    }
-                    final Rect bounds = (Rect) msg.obj;
-                    mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
-                    break;
-                }
-            }
-        }
-    };
-    private Messenger mToControllerMessenger;
-    private Messenger mMessenger = new Messenger(mHandler);
-
-    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
-        // manipulating the PIP in the same touch gesture
-        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
-
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.pip_menu_activity);
-
-        mAccessibilityManager = getSystemService(AccessibilityManager.class);
-        mBackgroundDrawable = new ColorDrawable(Color.BLACK);
-        mBackgroundDrawable.setAlpha(0);
-        mViewRoot = findViewById(R.id.background);
-        mViewRoot.setBackground(mBackgroundDrawable);
-        mMenuContainer = findViewById(R.id.menu_container);
-        mMenuContainer.setAlpha(0);
-        mTopEndContainer = findViewById(R.id.top_end_container);
-        mSettingsButton = findViewById(R.id.settings);
-        mSettingsButton.setAlpha(0);
-        mSettingsButton.setOnClickListener((v) -> {
-            if (v.getAlpha() != 0) {
-                showSettings();
-            }
-        });
-        mDismissButton = findViewById(R.id.dismiss);
-        mDismissButton.setAlpha(0);
-        mDismissButton.setOnClickListener(v -> dismissPip());
-        findViewById(R.id.expand_button).setOnClickListener(v -> {
-            if (mMenuContainer.getAlpha() != 0) {
-                expandPip();
-            }
-        });
-        mResizeHandle = findViewById(R.id.resize_handle);
-        mResizeHandle.setAlpha(0);
-        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());
-        setTitle(R.string.pip_menu_title);
-        setDisablePreviewScreenshots(true);
-
-        // Hide without an animation.
-        getWindow().setExitTransition(null);
-
-        initAccessibility();
-    }
-
-    private void initAccessibility() {
-        getWindow().getDecorView().setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            @Override
-            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(host, info);
-                String label = getResources().getString(R.string.pip_menu_title);
-                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
-            }
-
-            @Override
-            public boolean performAccessibilityAction(View host, int action, Bundle args) {
-                if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
-                    Message m = Message.obtain();
-                    m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
-                    sendMessage(m, "Could not notify controller to show PIP menu");
-                }
-                return super.performAccessibilityAction(host, action, args);
-            }
-        });
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
-            hideMenu();
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        updateFromIntent(intent);
-    }
-
-    @Override
-    public void onUserInteraction() {
-        if (mAllowMenuTimeout) {
-            repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
-        }
-    }
-
-    @Override
-    protected void onUserLeaveHint() {
-        super.onUserLeaveHint();
-
-        // If another task is starting on top of the menu, then hide and finish it so that it can be
-        // recreated on the top next time it starts
-        hideMenu();
-    }
-
-    @Override
-    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
-        super.onTopResumedActivityChanged(isTopResumedActivity);
-        if (!isTopResumedActivity && mMenuState != MENU_STATE_NONE) {
-            hideMenu();
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // In cases such as device lock, hide and finish it so that it can be recreated on the top
-        // next time it starts, see also {@link #onUserLeaveHint}
-        hideMenu();
-        cancelDelayedFinish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // Fallback, if we are destroyed for any other reason (like when the task is being reset),
-        // also reset the callback.
-        notifyActivityCallback(null);
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        if (!isInPictureInPictureMode) {
-            finish();
-        }
-    }
-
-    /**
-     * Dispatch a pointer event from {@link PipTouchHandler}.
-     */
-    private void dispatchPointerEvent(MotionEvent event) {
-        if (event.isTouchEvent()) {
-            dispatchTouchEvent(event);
-        } else {
-            dispatchGenericMotionEvent(event);
-        }
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (!mAllowTouches) {
-            return false;
-        }
-
-        // On the first action outside the window, hide the menu
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_OUTSIDE:
-                hideMenu();
-                return true;
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
-    public void finish() {
-        notifyActivityCallback(null);
-        super.finish();
-    }
-
-    @Override
-    public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
-        // Do nothing
-    }
-
-    private void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
-            boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
-        mAllowMenuTimeout = allowMenuTimeout;
-        if (mMenuState != menuState) {
-            // Disallow touches if the menu needs to resize while showing, and we are transitioning
-            // to/from a full menu state.
-            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow &&
-                    (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
-            mAllowTouches = !disallowTouchesUntilAnimationEnd;
-            cancelDelayedFinish();
-            updateActionViews(stackBounds);
-            if (mMenuContainerAnimator != null) {
-                mMenuContainerAnimator.cancel();
-            }
-            mMenuContainerAnimator = new AnimatorSet();
-            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
-                    mMenuContainer.getAlpha(), 1f);
-            menuAnim.addUpdateListener(mMenuBgUpdateListener);
-            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
-                    mSettingsButton.getAlpha(), 1f);
-            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
-                    mDismissButton.getAlpha(), 1f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(),
-                    ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
-                            ? 1f : 0f);
-            if (menuState == MENU_STATE_FULL) {
-                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
-                        resizeAnim);
-            } else {
-                mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
-            }
-            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
-            mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
-                    ? MENU_FADE_DURATION
-                    : MENU_SLOW_FADE_DURATION);
-            if (allowMenuTimeout) {
-                mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        repostDelayedFinish(INITIAL_DISMISS_DELAY);
-                    }
-                });
-            }
-            if (withDelay) {
-                // starts the menu container animation after window expansion is completed
-                notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_MENU_EXPANDED);
-            } else {
-                notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_INVALID_TYPE);
-                mMenuContainerAnimator.start();
-            }
-        } else {
-            // If we are already visible, then just start the delayed dismiss and unregister any
-            // existing input consumers from the previous drag
-            if (allowMenuTimeout) {
-                repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
-            }
-        }
-    }
-
-    /**
-     * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
-     * and instead, it fades out the controls by setting the alpha to 0 directly without menu
-     * visibility callbacks invoked.
-     */
-    private void fadeOutMenu() {
-        mMenuContainer.setAlpha(0f);
-        mSettingsButton.setAlpha(0f);
-        mDismissButton.setAlpha(0f);
-        mResizeHandle.setAlpha(0f);
-    }
-
-    private void hideMenu() {
-        hideMenu(null);
-    }
-
-    private void hideMenu(Runnable animationEndCallback) {
-        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false /* isDismissing */,
-                true /* animate */);
-    }
-
-    private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
-            boolean isDismissing, boolean animate) {
-        if (mMenuState != MENU_STATE_NONE) {
-            cancelDelayedFinish();
-            if (notifyMenuVisibility) {
-                notifyMenuStateChange(MENU_STATE_NONE, mResize, MESSAGE_INVALID_TYPE);
-            }
-            mMenuContainerAnimator = new AnimatorSet();
-            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
-                    mMenuContainer.getAlpha(), 0f);
-            menuAnim.addUpdateListener(mMenuBgUpdateListener);
-            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
-                    mSettingsButton.getAlpha(), 0f);
-            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
-                    mDismissButton.getAlpha(), 0f);
-            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
-            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
-            mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
-            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (animationFinishedRunnable != null) {
-                        animationFinishedRunnable.run();
-                    }
-
-                    if (!isDismissing) {
-                        // If we are dismissing the PiP, then don't try to pre-emptively finish the
-                        // menu activity
-                        finish();
-                    }
-                }
-            });
-            mMenuContainerAnimator.start();
-        } else {
-            // If the menu is not visible, just finish now
-            finish();
-        }
-    }
-
-    private void updateFromIntent(Intent intent) {
-        mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
-        if (mToControllerMessenger == null) {
-            Log.w(TAG, "Controller messenger is null. Stopping.");
-            finish();
-            return;
-        }
-        notifyActivityCallback(mMessenger);
-
-        ParceledListSlice<RemoteAction> actions = intent.getParcelableExtra(EXTRA_ACTIONS);
-        if (actions != null) {
-            mActions.clear();
-            mActions.addAll(actions.getList());
-        }
-
-        final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE);
-        if (menuState != MENU_STATE_NONE) {
-            Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
-            boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true);
-            boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false);
-            boolean withDelay = intent.getBooleanExtra(EXTRA_SHOW_MENU_WITH_DELAY, false);
-            boolean showResizeHandle = intent.getBooleanExtra(EXTRA_SHOW_RESIZE_HANDLE, false);
-            showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
-                    showResizeHandle);
-        }
-    }
-
-    private void setActions(Rect stackBounds, List<RemoteAction> actions) {
-        mActions.clear();
-        mActions.addAll(actions);
-        updateActionViews(stackBounds);
-    }
-
-    private void updateActionViews(Rect stackBounds) {
-        ViewGroup expandContainer = findViewById(R.id.expand_container);
-        ViewGroup actionsContainer = findViewById(R.id.actions_container);
-        actionsContainer.setOnTouchListener((v, ev) -> {
-            // Do nothing, prevent click through to parent
-            return true;
-        });
-
-        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
-            actionsContainer.setVisibility(View.INVISIBLE);
-        } else {
-            actionsContainer.setVisibility(View.VISIBLE);
-            if (mActionsGroup != null) {
-                // Ensure we have as many buttons as actions
-                final LayoutInflater inflater = LayoutInflater.from(this);
-                while (mActionsGroup.getChildCount() < mActions.size()) {
-                    final ImageButton actionView = (ImageButton) inflater.inflate(
-                            R.layout.pip_menu_action, mActionsGroup, false);
-                    mActionsGroup.addView(actionView);
-                }
-
-                // Update the visibility of all views
-                for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
-                    mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
-                            ? View.VISIBLE
-                            : View.GONE);
-                }
-
-                // Recreate the layout
-                final boolean isLandscapePip = stackBounds != null &&
-                        (stackBounds.width() > stackBounds.height());
-                for (int i = 0; i < mActions.size(); i++) {
-                    final RemoteAction action = mActions.get(i);
-                    final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i);
-
-                    // TODO: Check if the action drawable has changed before we reload it
-                    action.getIcon().loadDrawableAsync(this, d -> {
-                        d.setTint(Color.WHITE);
-                        actionView.setImageDrawable(d);
-                    }, mHandler);
-                    actionView.setContentDescription(action.getContentDescription());
-                    if (action.isEnabled()) {
-                        actionView.setOnClickListener(v -> {
-                            mHandler.post(() -> {
-                                try {
-                                    action.getActionIntent().send();
-                                } catch (CanceledException e) {
-                                    Log.w(TAG, "Failed to send action", e);
-                                }
-                            });
-                        });
-                    }
-                    actionView.setEnabled(action.isEnabled());
-                    actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
-
-                    // Update the margin between actions
-                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
-                            actionView.getLayoutParams();
-                    lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
-                }
-            }
-
-            // Update the expand container margin to adjust the center of the expand button to
-            // account for the existence of the action container
-            FrameLayout.LayoutParams expandedLp =
-                    (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
-            expandedLp.topMargin = getResources().getDimensionPixelSize(
-                    R.dimen.pip_action_padding);
-            expandedLp.bottomMargin = getResources().getDimensionPixelSize(
-                    R.dimen.pip_expand_container_edge_margin);
-            expandContainer.requestLayout();
-        }
-    }
-
-    private void updateDismissFraction(float fraction) {
-        int alpha;
-        final float menuAlpha = 1 - fraction;
-        if (mMenuState == MENU_STATE_FULL) {
-            mMenuContainer.setAlpha(menuAlpha);
-            mSettingsButton.setAlpha(menuAlpha);
-            mDismissButton.setAlpha(menuAlpha);
-            final float interpolatedAlpha =
-                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
-            alpha = (int) (interpolatedAlpha * 255);
-        } else {
-            if (mMenuState == MENU_STATE_CLOSE) {
-                mDismissButton.setAlpha(menuAlpha);
-            }
-            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
-        }
-        mBackgroundDrawable.setAlpha(alpha);
-    }
-
-    private void notifyMenuStateChange(int menuState, boolean resize, int callbackWhat) {
-        mMenuState = menuState;
-        mResize = resize;
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_MENU_STATE_CHANGED;
-        m.arg1 = menuState;
-        m.arg2 = resize ? 1 : 0;
-        if (callbackWhat != MESSAGE_INVALID_TYPE) {
-            // This message could be sent across processes when in secondary user.
-            // Make the receiver end sending back via our own Messenger
-            m.replyTo = mMessenger;
-            final Bundle data = new Bundle(1);
-            data.putInt(PipMenuActivityController.EXTRA_MESSAGE_CALLBACK_WHAT, callbackWhat);
-            m.obj = data;
-        }
-        sendMessage(m, "Could not notify controller of PIP menu visibility");
-    }
-
-    private void expandPip() {
-        // Do not notify menu visibility when hiding the menu, the controller will do this when it
-        // handles the message
-        hideMenu(() -> {
-            sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
-                    "Could not notify controller to expand PIP");
-        }, false /* notifyMenuVisibility */, false /* isDismissing */, true /* animate */);
-    }
-
-    private void dismissPip() {
-        // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
-        // we want to disable animating the fadeout animation of the buttons in order to call on
-        // PipTouchHandler#onPipDismiss fast enough.
-        final boolean animate = mMenuState != MENU_STATE_CLOSE;
-        // Do not notify menu visibility when hiding the menu, the controller will do this when it
-        // handles the message
-        hideMenu(() -> {
-            sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
-                    "Could not notify controller to dismiss PIP");
-        }, false /* notifyMenuVisibility */, true /* isDismissing */, animate);
-    }
-
-    private void showSettings() {
-        final Pair<ComponentName, Integer> topPipActivityInfo =
-                PipUtils.getTopPipActivity(this, ActivityManager.getService());
-        if (topPipActivityInfo.first != null) {
-            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
-            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
-                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
-            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
-            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-            startActivity(settingsIntent);
-        }
-    }
-
-    private void notifyActivityCallback(Messenger callback) {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
-        m.replyTo = callback;
-        m.arg1 = mResize ?  1 : 0;
-        sendMessage(m, "Could not notify controller of activity finished");
-    }
-
-    private void sendEmptyMessage(int what, String errorMsg) {
-        Message m = Message.obtain();
-        m.what = what;
-        sendMessage(m, errorMsg);
-    }
-
-    private void sendMessage(Message m, String errorMsg) {
-        if (mToControllerMessenger == null) {
-            return;
-        }
-        try {
-            mToControllerMessenger.send(m);
-        } catch (RemoteException e) {
-            Log.e(TAG, errorMsg, e);
-        }
-    }
-
-    private void cancelDelayedFinish() {
-        mHandler.removeCallbacks(mFinishRunnable);
-    }
-
-    private void repostDelayedFinish(int delay) {
-        int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
-                FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
-        mHandler.removeCallbacks(mFinishRunnable);
-        mHandler.postDelayed(mFinishRunnable, recommendedTimeout);
-    }
-}
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 383f6b3..873ba26 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -19,27 +19,20 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager.StackInfo;
-import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.RemoteAction;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.os.Debug;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.WindowManager;
 
+import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.pip.phone.PipMediaController.ActionListener;
 import com.android.systemui.shared.system.InputConsumerController;
 
@@ -58,30 +51,10 @@
     private static final String TAG = "PipMenuActController";
     private static final boolean DEBUG = false;
 
-    public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
-    public static final String EXTRA_ACTIONS = "actions";
-    public static final String EXTRA_STACK_BOUNDS = "stack_bounds";
-    public static final String EXTRA_ALLOW_TIMEOUT = "allow_timeout";
-    public static final String EXTRA_WILL_RESIZE_MENU = "resize_menu_on_show";
-    public static final String EXTRA_DISMISS_FRACTION = "dismiss_fraction";
-    public static final String EXTRA_MENU_STATE = "menu_state";
-    public static final String EXTRA_SHOW_MENU_WITH_DELAY = "show_menu_with_delay";
-    public static final String EXTRA_SHOW_RESIZE_HANDLE = "show_resize_handle";
-    public static final String EXTRA_MESSAGE_CALLBACK_WHAT = "message_callback_what";
-
-    public static final int MESSAGE_MENU_STATE_CHANGED = 100;
-    public static final int MESSAGE_EXPAND_PIP = 101;
-    public static final int MESSAGE_DISMISS_PIP = 103;
-    public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104;
-    public static final int MESSAGE_SHOW_MENU = 107;
-
     public static final int MENU_STATE_NONE = 0;
     public static final int MENU_STATE_CLOSE = 1;
     public static final int MENU_STATE_FULL = 2;
 
-    // The duration to wait before we consider the start activity as having timed out
-    private static final long START_ACTIVITY_REQUEST_TIMEOUT_MS = 300;
-
     /**
      * A listener interface to receive notification on changes in PIP.
      */
@@ -110,9 +83,8 @@
         void onPipShowMenu();
     }
 
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    private Class<?> mPipMenuActivityClass;
     private Context mContext;
+    private PipTaskOrganizer mPipTaskOrganizer;
     private PipMediaController mMediaController;
     private InputConsumerController mInputConsumerController;
 
@@ -121,63 +93,7 @@
     private ParceledListSlice<RemoteAction> mMediaActions;
     private int mMenuState;
 
-    // The dismiss fraction update is sent frequently, so use a temporary bundle for the message
-    private Bundle mTmpDismissFractionData = new Bundle();
-
-    private Runnable mOnAnimationEndRunnable;
-    private boolean mStartActivityRequested;
-    private long mStartActivityRequestedTime;
-    private Messenger mToActivityMessenger;
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_MENU_STATE_CHANGED: {
-                    final int menuState = msg.arg1;
-                    final boolean resize = msg.arg2 != 0;
-                    onMenuStateChanged(menuState, resize,
-                            getMenuStateChangeFinishedCallback(msg.replyTo, (Bundle) msg.obj));
-                    break;
-                }
-                case MESSAGE_EXPAND_PIP: {
-                    mListeners.forEach(Listener::onPipExpand);
-                    break;
-                }
-                case MESSAGE_DISMISS_PIP: {
-                    mListeners.forEach(Listener::onPipDismiss);
-                    break;
-                }
-                case MESSAGE_SHOW_MENU: {
-                    mListeners.forEach(Listener::onPipShowMenu);
-                    break;
-                }
-                case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
-                    mToActivityMessenger = msg.replyTo;
-                    setStartActivityRequested(false);
-                    if (mOnAnimationEndRunnable != null) {
-                        mOnAnimationEndRunnable.run();
-                        mOnAnimationEndRunnable = null;
-                    }
-                    // Mark the menu as invisible once the activity finishes as well
-                    if (mToActivityMessenger == null) {
-                        final boolean resize = msg.arg1 != 0;
-                        onMenuStateChanged(MENU_STATE_NONE, resize, null /* callback */);
-                    }
-                    break;
-                }
-            }
-        }
-    };
-    private Messenger mMessenger = new Messenger(mHandler);
-
-    private Runnable mStartActivityRequestedTimeoutRunnable = () -> {
-        setStartActivityRequested(false);
-        if (mOnAnimationEndRunnable != null) {
-            mOnAnimationEndRunnable.run();
-            mOnAnimationEndRunnable = null;
-        }
-        Log.e(TAG, "Expected start menu activity request timed out");
-    };
+    private PipMenuView mPipMenuView;
 
     private ActionListener mMediaActionListener = new ActionListener() {
         @Override
@@ -187,39 +103,40 @@
         }
     };
 
-    public PipMenuActivityController(Context context, Class<?> pipMenuActivityClass,
-            PipMediaController mediaController, InputConsumerController inputConsumerController
+    public PipMenuActivityController(Context context,
+            PipMediaController mediaController, InputConsumerController inputConsumerController,
+            PipTaskOrganizer pipTaskOrganizer
     ) {
         mContext = context;
         mMediaController = mediaController;
         mInputConsumerController = inputConsumerController;
-        mPipMenuActivityClass = pipMenuActivityClass;
+        mPipTaskOrganizer = pipTaskOrganizer;
     }
 
-    public boolean isMenuActivityVisible() {
-        return mToActivityMessenger != null;
+    public boolean isMenuVisible() {
+        return mPipMenuView != null && mMenuState != MENU_STATE_NONE;
     }
 
     public void onActivityPinned() {
+        if (mPipMenuView == null) {
+            WindowManager.LayoutParams lp =
+                    getPipMenuLayoutParams(0, 0);
+            mPipMenuView = new PipMenuView(mContext, this);
+            mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, lp);
+        }
         mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
     }
 
     public void onActivityUnpinned() {
         hideMenu();
         mInputConsumerController.unregisterInputConsumer();
-        setStartActivityRequested(false);
+        mPipTaskOrganizer.detachPipMenuViewHost();
+        mPipMenuView = null;
     }
 
     public void onPinnedStackAnimationEnded() {
-        // Note: Only active menu activities care about this event
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_ANIMATION_ENDED;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu pinned animation ended", e);
-            }
+        if (isMenuVisible()) {
+            mPipMenuView.onPipAnimationEnded();
         }
     }
 
@@ -236,27 +153,13 @@
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
     public void setDismissFraction(float fraction) {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null)
+            Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible
                     + " fraction=" + fraction);
         }
-        if (mToActivityMessenger != null) {
-            mTmpDismissFractionData.clear();
-            mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction);
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION;
-            m.obj = mTmpDismissFractionData;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to update dismiss fraction", e);
-            }
-        } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
-            // If we haven't requested the start activity, or if it previously took too long to
-            // start, then start it
-            startMenuActivity(MENU_STATE_NONE, null /* stackBounds */,
-                    false /* allowMenuTimeout */, false /* resizeMenuOnShow */,
-                    false /* withDelay */, false /* showResizeHandle */);
+        if (isMenuVisible) {
+            mPipMenuView.updateDismissFraction(fraction);
         }
     }
 
@@ -282,27 +185,11 @@
                 false /* withDelay */, showResizeHandle);
     }
 
-    private Runnable getMenuStateChangeFinishedCallback(@Nullable Messenger replyTo,
-            @Nullable Bundle data) {
-        if (replyTo == null || data == null) {
-            return null;
-        }
-        return () -> {
-            try {
-                final Message m = Message.obtain();
-                m.what = data.getInt(EXTRA_MESSAGE_CALLBACK_WHAT);
-                replyTo.send(m);
-            } catch (RemoteException e) {
-                // ignored
-            }
-        };
-    }
-
     private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
             boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
         if (DEBUG) {
             Log.d(TAG, "showMenu() state=" + menuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
+                    + " isMenuVisible=" + isMenuVisible()
                     + " allowMenuTimeout=" + allowMenuTimeout
                     + " willResizeMenu=" + willResizeMenu
                     + " withDelay=" + withDelay
@@ -310,64 +197,34 @@
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
 
-        if (mToActivityMessenger != null) {
-            Bundle data = new Bundle();
-            data.putInt(EXTRA_MENU_STATE, menuState);
-            if (stackBounds != null) {
-                data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
-            }
-            data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
-            data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
-            data.putBoolean(EXTRA_SHOW_MENU_WITH_DELAY, withDelay);
-            data.putBoolean(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle);
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
-            m.obj = data;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to show", e);
-            }
-        } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
-            // If we haven't requested the start activity, or if it previously took too long to
-            // start, then start it
-            startMenuActivity(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
-                    showResizeHandle);
+        if (mPipMenuView == null) {
+            Log.e(TAG, "PipMenu has not been attached yet.");
+            return;
         }
+        mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
+                showResizeHandle);
     }
 
     /**
      * Pokes the menu, indicating that the user is interacting with it.
      */
     public void pokeMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null));
+            Log.d(TAG, "pokeMenu() isMenuVisible=" + isMenuVisible);
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_POKE_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify poke menu", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.pokeMenu();
         }
     }
 
     private void fadeOutMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
-            Log.d(TAG, "fadeOutMenu() state=" + mMenuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
-                    + " callers=\n" + Debug.getCallers(5, "    "));
+            Log.d(TAG, "fadeOutMenu() isMenuVisible=" + isMenuVisible);
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_FADE_OUT_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to fade out", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.fadeOutMenu();
         }
     }
 
@@ -375,19 +232,14 @@
      * Hides the menu activity.
      */
     public void hideMenu() {
+        final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
             Log.d(TAG, "hideMenu() state=" + mMenuState
-                    + " hasActivity=" + (mToActivityMessenger != null)
+                    + " isMenuVisible=" + isMenuVisible
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu to hide", e);
-            }
+        if (isMenuVisible) {
+            mPipMenuView.hideMenu();
         }
     }
 
@@ -395,29 +247,11 @@
      * Hides the menu activity.
      */
     public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) {
-        if (mStartActivityRequested) {
-            // If the menu has been start-requested, but not actually started, then we defer the
-            // trigger callback until the menu has started and called back to the controller.
-            mOnAnimationEndRunnable = onEndCallback;
-            onStartCallback.run();
-
-            // Fallback for b/63752800, we have started the PipMenuActivity but it has not made any
-            // callbacks. Don't continue to wait for the menu to show past some timeout.
-            mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable);
-            mHandler.postDelayed(mStartActivityRequestedTimeoutRunnable,
-                    START_ACTIVITY_REQUEST_TIMEOUT_MS);
-        } else if (mMenuState != MENU_STATE_NONE && mToActivityMessenger != null) {
+        if (isMenuVisible()) {
             // If the menu is visible in either the closed or full state, then hide the menu and
             // trigger the animation trigger afterwards
             onStartCallback.run();
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
-            m.obj = onEndCallback;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify hide menu", e);
-            }
+            mPipMenuView.hideMenu(onEndCallback);
         }
     }
 
@@ -438,6 +272,18 @@
         updateMenuActions();
     }
 
+    void onPipExpand() {
+        mListeners.forEach(Listener::onPipExpand);
+    }
+
+    void onPipDismiss() {
+        mListeners.forEach(Listener::onPipDismiss);
+    }
+
+    void onPipShowMenu() {
+        mListeners.forEach(Listener::onPipShowMenu);
+    }
+
     /**
      * @return the best set of actions to show in the PiP menu.
      */
@@ -449,47 +295,20 @@
     }
 
     /**
-     * Starts the menu activity on the top task of the pinned stack.
+     * Returns a default LayoutParams for the PIP Menu.
+     * @param width the PIP stack width.
+     * @param height the PIP stack height.
      */
-    private void startMenuActivity(int menuState, Rect stackBounds, boolean allowMenuTimeout,
-            boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
-        try {
-            StackInfo pinnedStackInfo = ActivityTaskManager.getService().getStackInfo(
-                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-            if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
-                    pinnedStackInfo.taskIds.length > 0) {
-                Intent intent = new Intent(mContext, mPipMenuActivityClass);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
-                intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
-                if (stackBounds != null) {
-                    intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds);
-                }
-                intent.putExtra(EXTRA_MENU_STATE, menuState);
-                intent.putExtra(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
-                intent.putExtra(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
-                intent.putExtra(EXTRA_SHOW_MENU_WITH_DELAY, withDelay);
-                intent.putExtra(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle);
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                options.setLaunchTaskId(
-                        pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
-                options.setTaskOverlay(true, true /* canResume */);
-                mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
-                setStartActivityRequested(true);
-            } else {
-                Log.e(TAG, "No PIP tasks found");
-            }
-        } catch (RemoteException e) {
-            setStartActivityRequested(false);
-            Log.e(TAG, "Error showing PIP menu activity", e);
-        }
+    public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) {
+        return new WindowManager.LayoutParams(width, height,
+                WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSLUCENT);
     }
 
     /**
-     * Updates the PiP menu activity with the best set of actions provided.
+     * Updates the PiP menu with the best set of actions provided.
      */
     private void updateMenuActions() {
-        if (mToActivityMessenger != null) {
+        if (isMenuVisible()) {
             // Fetch the pinned stack bounds
             Rect stackBounds = null;
             try {
@@ -499,20 +318,10 @@
                     stackBounds = pinnedStackInfo.bounds;
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Error showing PIP menu activity", e);
+                Log.e(TAG, "Error showing PIP menu", e);
             }
 
-            Bundle data = new Bundle();
-            data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
-            data.putParcelable(EXTRA_ACTIONS, resolveMenuActions());
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS;
-            m.obj = data;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu activity to update actions", e);
-            }
+            mPipMenuView.setActions(stackBounds, resolveMenuActions().getList());
         }
     }
 
@@ -524,17 +333,9 @@
     }
 
     /**
-     * @return whether the time of the activity request has exceeded the timeout.
-     */
-    private boolean isStartActivityRequestedElapsed() {
-        return (SystemClock.uptimeMillis() - mStartActivityRequestedTime)
-                >= START_ACTIVITY_REQUEST_TIMEOUT_MS;
-    }
-
-    /**
      * Handles changes in menu visibility.
      */
-    private void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
+    void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
         if (DEBUG) {
             Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
                     + " menuState=" + menuState + " resize=" + resize
@@ -556,25 +357,14 @@
         mMenuState = menuState;
     }
 
-    private void setStartActivityRequested(boolean requested) {
-        mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable);
-        mStartActivityRequested = requested;
-        mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0;
-    }
-
     /**
      * Handles a pointer event sent from pip input consumer.
      */
     void handlePointerEvent(MotionEvent ev) {
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_POINTER_EVENT;
-            m.obj = ev;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not dispatch touch event", e);
-            }
+        if (ev.isTouchEvent()) {
+            mPipMenuView.dispatchTouchEvent(ev);
+        } else {
+            mPipMenuView.dispatchGenericMotionEvent(ev);
         }
     }
 
@@ -582,15 +372,14 @@
      * Tell the PIP Menu to recalculate its layout given its current position on the display.
      */
     public void updateMenuLayout(Rect bounds) {
-        if (mToActivityMessenger != null) {
-            Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_UPDATE_MENU_LAYOUT;
-            m.obj = bounds;
-            try {
-                mToActivityMessenger.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not dispatch touch event", e);
-            }
+        final boolean isMenuVisible = isMenuVisible();
+        if (DEBUG) {
+            Log.d(TAG, "updateMenuLayout() state=" + mMenuState
+                    + " isMenuVisible=" + isMenuVisible
+                    + " callers=\n" + Debug.getCallers(5, "    "));
+        }
+        if (isMenuVisible) {
+            mPipMenuView.updateMenuLayout(bounds);
         }
     }
 
@@ -598,9 +387,7 @@
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mMenuState=" + mMenuState);
-        pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger);
+        pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView);
         pw.println(innerPrefix + "mListeners=" + mListeners.size());
-        pw.println(innerPrefix + "mStartActivityRequested=" + mStartActivityRequested);
-        pw.println(innerPrefix + "mStartActivityRequestedTime=" + mStartActivityRequestedTime);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
new file mode 100644
index 0000000..993bfe0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.phone;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Translucent window that gets started on top of a task in PIP to allow the user to control it.
+ */
+public class PipMenuView extends FrameLayout {
+
+    private static final String TAG = "PipMenuView";
+
+    private static final int MESSAGE_INVALID_TYPE = -1;
+    public static final int MESSAGE_MENU_EXPANDED = 8;
+
+    private static final int INITIAL_DISMISS_DELAY = 3500;
+    private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
+    private static final long MENU_FADE_DURATION = 125;
+    private static final long MENU_SLOW_FADE_DURATION = 175;
+    private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
+
+    private static final float MENU_BACKGROUND_ALPHA = 0.3f;
+    private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
+
+    private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
+    private static final boolean ENABLE_RESIZE_HANDLE = false;
+
+    private int mMenuState;
+    private boolean mResize = true;
+    private boolean mAllowMenuTimeout = true;
+    private boolean mAllowTouches = true;
+
+    private final List<RemoteAction> mActions = new ArrayList<>();
+
+    private AccessibilityManager mAccessibilityManager;
+    private Drawable mBackgroundDrawable;
+    private View mMenuContainer;
+    private LinearLayout mActionsGroup;
+    private int mBetweenActionPaddingLand;
+
+    private AnimatorSet mMenuContainerAnimator;
+    private PipMenuActivityController mController;
+
+    private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float alpha = (float) animation.getAnimatedValue();
+                    mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255));
+                }
+            };
+
+    private Handler mHandler = new Handler();
+
+    private final Runnable mHideMenuRunnable = this::hideMenu;
+
+    protected View mViewRoot;
+    protected View mSettingsButton;
+    protected View mDismissButton;
+    protected View mResizeHandle;
+    protected View mTopEndContainer;
+    protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+
+    public PipMenuView(Context context, PipMenuActivityController controller) {
+        super(context, null, 0);
+        mContext = context;
+        mController = controller;
+
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        inflate(context, R.layout.pip_menu, this);
+
+        mBackgroundDrawable = new ColorDrawable(Color.BLACK);
+        mBackgroundDrawable.setAlpha(0);
+        mViewRoot = findViewById(R.id.background);
+        mViewRoot.setBackground(mBackgroundDrawable);
+        mMenuContainer = findViewById(R.id.menu_container);
+        mMenuContainer.setAlpha(0);
+        mTopEndContainer = findViewById(R.id.top_end_container);
+        mSettingsButton = findViewById(R.id.settings);
+        mSettingsButton.setAlpha(0);
+        mSettingsButton.setOnClickListener((v) -> {
+            if (v.getAlpha() != 0) {
+                showSettings();
+            }
+        });
+        mDismissButton = findViewById(R.id.dismiss);
+        mDismissButton.setAlpha(0);
+        mDismissButton.setOnClickListener(v -> dismissPip());
+        findViewById(R.id.expand_button).setOnClickListener(v -> {
+            if (mMenuContainer.getAlpha() != 0) {
+                expandPip();
+            }
+        });
+        // TODO (b/161710689): Remove this once focusability for Windowless window is working
+        findViewById(R.id.expand_button).setFocusable(false);
+        mDismissButton.setFocusable(false);
+        mSettingsButton.setFocusable(false);
+
+        mResizeHandle = findViewById(R.id.resize_handle);
+        mResizeHandle.setAlpha(0);
+        mActionsGroup = findViewById(R.id.actions_group);
+        mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
+                R.dimen.pip_between_action_padding_land);
+        mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
+        mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
+                mResizeHandle, mSettingsButton, mDismissButton);
+
+        initAccessibility();
+    }
+
+    private void initAccessibility() {
+        this.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                String label = getResources().getString(R.string.pip_menu_title);
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
+            }
+
+            @Override
+            public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
+                    mController.onPipShowMenu();
+                }
+                return super.performAccessibilityAction(host, action, args);
+            }
+        });
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+            hideMenu();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (!mAllowTouches) {
+            return false;
+        }
+
+        if (mAllowMenuTimeout) {
+            repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+        }
+
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        if (mAllowMenuTimeout) {
+            repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+        }
+
+        return super.dispatchGenericMotionEvent(event);
+    }
+
+    void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+            boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
+        mAllowMenuTimeout = allowMenuTimeout;
+        if (mMenuState != menuState) {
+            // Disallow touches if the menu needs to resize while showing, and we are transitioning
+            // to/from a full menu state.
+            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow
+                    && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+            mAllowTouches = !disallowTouchesUntilAnimationEnd;
+            cancelDelayedHide();
+            updateActionViews(stackBounds);
+            if (mMenuContainerAnimator != null) {
+                mMenuContainerAnimator.cancel();
+            }
+            mMenuContainerAnimator = new AnimatorSet();
+            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 1f);
+            menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 1f);
+            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+                    mDismissButton.getAlpha(), 1f);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(),
+                    ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
+                            ? 1f : 0f);
+            if (menuState == MENU_STATE_FULL) {
+                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
+                        resizeAnim);
+            } else {
+                mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
+            }
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
+            mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
+                    ? MENU_FADE_DURATION
+                    : MENU_SLOW_FADE_DURATION);
+            if (allowMenuTimeout) {
+                mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        repostDelayedHide(INITIAL_DISMISS_DELAY);
+                    }
+                });
+            }
+            if (withDelay) {
+                // starts the menu container animation after window expansion is completed
+                notifyMenuStateChange(menuState, resizeMenuOnShow, () -> {
+                    if (mMenuContainerAnimator == null) {
+                        return;
+                    }
+                    mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
+                    mMenuContainerAnimator.start();
+                });
+            } else {
+                notifyMenuStateChange(menuState, resizeMenuOnShow, null);
+                mMenuContainerAnimator.start();
+            }
+        } else {
+            // If we are already visible, then just start the delayed dismiss and unregister any
+            // existing input consumers from the previous drag
+            if (allowMenuTimeout) {
+                repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+            }
+        }
+    }
+
+    /**
+     * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
+     * and instead, it fades out the controls by setting the alpha to 0 directly without menu
+     * visibility callbacks invoked.
+     */
+    void fadeOutMenu() {
+        mMenuContainer.setAlpha(0f);
+        mSettingsButton.setAlpha(0f);
+        mDismissButton.setAlpha(0f);
+        mResizeHandle.setAlpha(0f);
+    }
+
+    void pokeMenu() {
+        cancelDelayedHide();
+    }
+
+    void onPipAnimationEnded() {
+        mAllowTouches = true;
+    }
+
+    void updateMenuLayout(Rect bounds) {
+        mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
+    }
+
+    void hideMenu() {
+        hideMenu(null);
+    }
+
+    void hideMenu(Runnable animationEndCallback) {
+        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false);
+    }
+
+    private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
+            boolean animate) {
+        if (mMenuState != MENU_STATE_NONE) {
+            cancelDelayedHide();
+            if (notifyMenuVisibility) {
+                notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
+            }
+            mMenuContainerAnimator = new AnimatorSet();
+            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 0f);
+            menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 0f);
+            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+                    mDismissButton.getAlpha(), 0f);
+            ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
+                    mResizeHandle.getAlpha(), 0f);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+            mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
+            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (animationFinishedRunnable != null) {
+                        animationFinishedRunnable.run();
+                    }
+                }
+            });
+            mMenuContainerAnimator.start();
+        }
+    }
+
+    void setActions(Rect stackBounds, List<RemoteAction> actions) {
+        mActions.clear();
+        mActions.addAll(actions);
+        updateActionViews(stackBounds);
+    }
+
+    private void updateActionViews(Rect stackBounds) {
+        ViewGroup expandContainer = findViewById(R.id.expand_container);
+        ViewGroup actionsContainer = findViewById(R.id.actions_container);
+        actionsContainer.setOnTouchListener((v, ev) -> {
+            // Do nothing, prevent click through to parent
+            return true;
+        });
+
+        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
+            actionsContainer.setVisibility(View.INVISIBLE);
+        } else {
+            actionsContainer.setVisibility(View.VISIBLE);
+            if (mActionsGroup != null) {
+                // Ensure we have as many buttons as actions
+                final LayoutInflater inflater = LayoutInflater.from(mContext);
+                while (mActionsGroup.getChildCount() < mActions.size()) {
+                    final ImageButton actionView = (ImageButton) inflater.inflate(
+                            R.layout.pip_menu_action, mActionsGroup, false);
+                    mActionsGroup.addView(actionView);
+                }
+
+                // Update the visibility of all views
+                for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+                    mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+                            ? View.VISIBLE
+                            : View.GONE);
+                }
+
+                // Recreate the layout
+                final boolean isLandscapePip = stackBounds != null
+                        && (stackBounds.width() > stackBounds.height());
+                for (int i = 0; i < mActions.size(); i++) {
+                    final RemoteAction action = mActions.get(i);
+                    final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i);
+
+                    // TODO: Check if the action drawable has changed before we reload it
+                    action.getIcon().loadDrawableAsync(mContext, d -> {
+                        d.setTint(Color.WHITE);
+                        actionView.setImageDrawable(d);
+                    }, mHandler);
+                    actionView.setContentDescription(action.getContentDescription());
+                    if (action.isEnabled()) {
+                        actionView.setOnClickListener(v -> {
+                            mHandler.post(() -> {
+                                try {
+                                    action.getActionIntent().send();
+                                } catch (CanceledException e) {
+                                    Log.w(TAG, "Failed to send action", e);
+                                }
+                            });
+                        });
+                    }
+                    actionView.setEnabled(action.isEnabled());
+                    actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+
+                    // Update the margin between actions
+                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+                            actionView.getLayoutParams();
+                    lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
+                }
+            }
+
+            // Update the expand container margin to adjust the center of the expand button to
+            // account for the existence of the action container
+            FrameLayout.LayoutParams expandedLp =
+                    (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
+            expandedLp.topMargin = getResources().getDimensionPixelSize(
+                    R.dimen.pip_action_padding);
+            expandedLp.bottomMargin = getResources().getDimensionPixelSize(
+                    R.dimen.pip_expand_container_edge_margin);
+            expandContainer.requestLayout();
+        }
+    }
+
+    void updateDismissFraction(float fraction) {
+        int alpha;
+        final float menuAlpha = 1 - fraction;
+        if (mMenuState == MENU_STATE_FULL) {
+            mMenuContainer.setAlpha(menuAlpha);
+            mSettingsButton.setAlpha(menuAlpha);
+            mDismissButton.setAlpha(menuAlpha);
+            final float interpolatedAlpha =
+                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
+            alpha = (int) (interpolatedAlpha * 255);
+        } else {
+            if (mMenuState == MENU_STATE_CLOSE) {
+                mDismissButton.setAlpha(menuAlpha);
+            }
+            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
+        }
+        mBackgroundDrawable.setAlpha(alpha);
+    }
+
+    private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
+        mMenuState = menuState;
+        mController.onMenuStateChanged(menuState, resize, callback);
+    }
+
+    private void expandPip() {
+        // Do not notify menu visibility when hiding the menu, the controller will do this when it
+        // handles the message
+        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
+    }
+
+    private void dismissPip() {
+        // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
+        // we want to disable animating the fadeout animation of the buttons in order to call on
+        // PipTouchHandler#onPipDismiss fast enough.
+        final boolean animate = mMenuState != MENU_STATE_CLOSE;
+        // Do not notify menu visibility when hiding the menu, the controller will do this when it
+        // handles the message
+        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
+    }
+
+    private void showSettings() {
+        final Pair<ComponentName, Integer> topPipActivityInfo =
+                PipUtils.getTopPipActivity(mContext, ActivityManager.getService());
+        if (topPipActivityInfo.first != null) {
+            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            mContext.startActivity(settingsIntent);
+        }
+    }
+
+    private void cancelDelayedHide() {
+        mHandler.removeCallbacks(mHideMenuRunnable);
+    }
+
+    private void repostDelayedHide(int delay) {
+        int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
+                FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
+        mHandler.removeCallbacks(mHideMenuRunnable);
+        mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
+    }
+}
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 19138fdb..dcee2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -23,6 +23,8 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.Choreographer;
 
@@ -66,6 +68,8 @@
     private PipMenuActivityController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
 
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
     /** PIP's current bounds on the screen. */
     private final Rect mBounds = new Rect();
 
@@ -128,8 +132,10 @@
                         SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
-        mMenuController.updateMenuLayout(newBounds);
-        mBounds.set(newBounds);
+        mMainHandler.post(() -> {
+            mMenuController.updateMenuLayout(newBounds);
+            mBounds.set(newBounds);
+        });
     };
 
     /**
@@ -253,7 +259,9 @@
                 mTemporaryBounds.set(toBounds);
                 mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds,
                         (Rect newBounds) -> {
-                            mMenuController.updateMenuLayout(newBounds);
+                            mMainHandler.post(() -> {
+                                mMenuController.updateMenuLayout(newBounds);
+                            });
                     });
             }
         } else {
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 9c42f8b..f6853ec 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -111,6 +111,7 @@
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
     private PipTaskOrganizer mPipTaskOrganizer;
+    private PipMenuActivityController mPipMenuActivityController;
     private PipUiEventLogger mPipUiEventLogger;
 
     private int mCtrlType;
@@ -119,7 +120,7 @@
             PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
             PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
             Runnable updateMovementBoundsRunnable, SysUiState sysUiState,
-            PipUiEventLogger pipUiEventLogger) {
+            PipUiEventLogger pipUiEventLogger, PipMenuActivityController menuActivityController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -129,6 +130,7 @@
         mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mSysUiState = sysUiState;
+        mPipMenuActivityController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
 
         context.getDisplay().getRealSize(mMaxSize);
@@ -298,6 +300,7 @@
         float x = ev.getX();
         float y = ev.getY();
         if (action == MotionEvent.ACTION_DOWN) {
+            final Rect currentPipBounds = mMotionHelper.getBounds();
             mLastResizeBounds.setEmpty();
             mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
             if (mAllowGesture) {
@@ -305,6 +308,10 @@
                 mDownPoint.set(x, y);
                 mLastDownBounds.set(mMotionHelper.getBounds());
             }
+            if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
+                    && mPipMenuActivityController.isMenuVisible()) {
+                mPipMenuActivityController.hideMenu();
+            }
 
         } else if (mAllowGesture) {
             switch (action) {
@@ -364,6 +371,10 @@
         mUserResizeBounds.set(bounds);
     }
 
+    void invalidateUserResizeBounds() {
+        mUserResizeBounds.setEmpty();
+    }
+
     Rect getUserResizeBounds() {
         return mUserResizeBounds;
     }
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 b20ea4e..9693f23 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -58,7 +58,6 @@
 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;
@@ -99,7 +98,6 @@
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
-    private final PipSnapAlgorithm mSnapAlgorithm;
     private final AccessibilityManager mAccessibilityManager;
     private boolean mShowPipMenuOnAnimationEnd = false;
 
@@ -216,24 +214,23 @@
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             DeviceConfigProxy deviceConfig,
-            PipSnapAlgorithm pipSnapAlgorithm,
             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, pipUiEventLogger);
+                        this::updateMovementBounds, sysUiState, pipUiEventLogger, menuController);
         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
@@ -248,11 +245,10 @@
         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;
 
@@ -419,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) {
@@ -437,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());
@@ -476,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
@@ -487,8 +497,8 @@
 
                 if (isExpanded) {
                     curBounds.set(mExpandedBounds);
-                    mSnapAlgorithm.applySnapFraction(curBounds, toMovementBounds,
-                            mSavedSnapFraction);
+                    mPipBoundsHandler.getSnapAlgorithm().applySnapFraction(curBounds,
+                            toMovementBounds, mSavedSnapFraction);
                 }
 
                 if (prevBottom < toBottom) {
@@ -595,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(
@@ -763,10 +773,7 @@
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
     private void updateDismissFraction() {
-        // Skip updating the dismiss fraction when the IME is showing. This is to work around an
-        // issue where starting the menu activity for the dismiss overlay will steal the window
-        // focus, which closes the IME.
-        if (mMenuController != null && !mIsImeShowing) {
+        if (mMenuController != null) {
             Rect bounds = mMotionHelper.getBounds();
             final float target = mInsetBounds.bottom;
             float fraction = 0f;
@@ -774,7 +781,7 @@
                 final float distance = bounds.bottom - target;
                 fraction = Math.min(distance / bounds.height(), 1f);
             }
-            if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuActivityVisible()) {
+            if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
                 // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
                 mMenuController.setDismissFraction(fraction);
             }
@@ -831,8 +838,8 @@
                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
                     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;
@@ -1012,25 +1019,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;
     }
@@ -1062,6 +1069,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/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index f1c8b0c..a388fa3 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.SplitScreen;
+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;
@@ -135,7 +142,6 @@
     // Used to calculate the movement bounds
     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
     private final Rect mTmpInsetBounds = new Rect();
-    private final Rect mTmpNormalBounds = new Rect();
 
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
@@ -209,10 +215,8 @@
         public void onMovementBoundsChanged(boolean fromImeAdjustment) {
             mHandler.post(() -> {
                 // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
-                final Rect destinationBounds = new Rect();
-                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
-                        destinationBounds, mTmpDisplayInfo);
-                mDefaultPipBounds.set(destinationBounds);
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBounds,
+                        mDefaultPipBounds, mTmpDisplayInfo);
             });
         }
 
@@ -229,17 +233,18 @@
 
     @Inject
     public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
-            PipBoundsHandler pipBoundsHandler,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipSurfaceTransactionHelper surfaceTransactionHelper,
-            Divider divider) {
+            ConfigurationController configController,
+            DisplayController displayController,
+            Optional<SplitScreen> splitScreenOptional,
+            @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();
@@ -248,7 +253,10 @@
 
         mResizeAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
-        mPipTaskOrganizer = pipTaskOrganizer;
+        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context, configController);
+        mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
+                mPipSurfaceTransactionHelper, splitScreenOptional, displayController,
+                pipUiEventLogger, shellTaskOrganizer);
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mActivityTaskManager = ActivityTaskManager.getService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -267,7 +275,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);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index 214088c..7037403 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -36,8 +36,8 @@
  * Activity to show the PIP menu to control PIP.
  */
 public class PipMenuActivity extends Activity implements PipManager.Listener {
-    private static final boolean DEBUG = false;
     private static final String TAG = "PipMenuActivity";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index 651a4f3..5e5de58 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -114,6 +114,14 @@
                 notifyPipNotification();
             }
         }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            if (updateMediaControllerMetadata() && mNotified) {
+                // update notification
+                notifyPipNotification();
+            }
+        }
     };
 
     private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
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/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
rename to packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index fe5fa2b..8b8941a 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -14,23 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.onehanded.dagger;
+package com.android.systemui.power.dagger;
 
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
+import com.android.systemui.power.PowerNotificationWarnings;
+import com.android.systemui.power.PowerUI;
 
 import dagger.Binds;
 import dagger.Module;
 
-/**
- * Dagger Module for One handed.
- */
+
+/** Dagger Module for code in the power package. */
 @Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
+public interface PowerModule {
+    /** */
     @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
-
+    PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index affc5ee..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,9 +42,8 @@
 import java.lang.ref.WeakReference
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class PrivacyItemController @Inject constructor(
     private val appOpsController: AppOpsController,
     @Main uiExecutor: DelayableExecutor,
@@ -57,7 +57,8 @@
     @VisibleForTesting
     internal companion object {
         val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
-                AppOpsManager.OP_RECORD_AUDIO)
+                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)
@@ -248,9 +249,11 @@
 
     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
         }
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/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 8740581..8ff96c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -22,6 +22,7 @@
 
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.qs.AutoAddTracker;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.HotspotController;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -56,4 +58,9 @@
         manager.init();
         return manager;
     }
+
+
+    /** */
+    @Binds
+    QSHost provideQsHost(QSTileHost controllerImpl);
 }
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/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index f89185e..1a7e229 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.SplitScreen;
 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<SplitScreen> mSplitScreenOptional;
 
     private Context mContext;
     private Handler mHandler;
@@ -66,9 +66,9 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,
-            Optional<Divider> dividerOptional) {
+            Optional<SplitScreen> splitScreenOptional) {
         mStatusBarLazy = statusBarLazy.orElse(null);
-        mDividerOptional = dividerOptional;
+        mSplitScreenOptional = splitScreenOptional;
     }
 
     @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);
-
+                    mSplitScreenOptional.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 d222489..263bbdb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -68,6 +68,7 @@
 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;
@@ -85,7 +86,7 @@
 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.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -97,16 +98,16 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.BiConsumer;
 
 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 {
@@ -123,7 +124,7 @@
     private final Context mContext;
     private final PipUI mPipUI;
     private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
-    private final Optional<Divider> mDividerOptional;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -232,7 +233,9 @@
             }
             long token = Binder.clearCallingIdentity();
             try {
-                mDividerOptional.ifPresent(Divider::onDockedFirstAnimationFrame);
+                mSplitScreenOptional.ifPresent(splitScreen -> {
+                    splitScreen.onDockedFirstAnimationFrame();
+                });
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -262,8 +265,8 @@
             }
             long token = Binder.clearCallingIdentity();
             try {
-                return mDividerOptional.map(
-                        divider -> divider.getView().getNonMinimizedSplitScreenSecondaryBounds())
+                return mSplitScreenOptional.map(splitScreen ->
+                        splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
                         .orElse(null);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -399,10 +402,8 @@
 
         @Override
         public void setSplitScreenMinimized(boolean minimized) {
-            Divider divider = mDividerOptional.get();
-            if (divider != null) {
-                divider.setMinimized(minimized);
-            }
+            mSplitScreenOptional.ifPresent(
+                    splitScreen -> splitScreen.setMinimized(minimized));
         }
 
         @Override
@@ -592,6 +593,8 @@
     };
 
     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
+    private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
+            this::notifySplitScreenBoundsChanged;
 
     // This is the death handler for the binder from the launcher service
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -602,7 +605,7 @@
     public OverviewProxyService(Context context, CommandQueue commandQueue,
             Lazy<NavigationBarController> navBarControllerLazy, NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            PipUI pipUI, Optional<Divider> dividerOptional,
+            PipUI pipUI, Optional<SplitScreen> splitScreenOptional,
             Optional<Lazy<StatusBar>> statusBarOptionalLazy, OneHandedUI oneHandedUI,
             BroadcastDispatcher broadcastDispatcher) {
         super(broadcastDispatcher);
@@ -613,7 +616,6 @@
         mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
         mConnectionBackoffAttempts = 0;
-        mDividerOptional = dividerOptional;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                 com.android.internal.R.string.config_recentsComponentName));
         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
@@ -653,6 +655,10 @@
         });
         mCommandQueue = commandQueue;
 
+        splitScreenOptional.ifPresent(splitScreen ->
+                splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
+        mSplitScreenOptional = splitScreenOptional;
+
         // Listen for user setup
         startTracking();
 
@@ -755,10 +761,8 @@
         startConnectionToCurrentUser();
 
         // Clean up the minimized state if launcher dies
-        Divider divider = mDividerOptional.get();
-        if (divider != null) {
-            divider.setMinimized(false);
-        }
+        mSplitScreenOptional.ifPresent(
+                splitScreen -> splitScreen.setMinimized(false));
     }
 
     public void startConnectionToCurrentUser() {
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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 4da2bb6..10a44dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -30,17 +30,17 @@
 
 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";
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 469c4a7..3bf118d 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;
@@ -41,7 +40,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
 import java.io.IOException;
@@ -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;
@@ -81,12 +79,12 @@
     private final Executor mLongExecutor;
     private final UiEventLogger mUiEventLogger;
     private final NotificationManager mNotificationManager;
-    private final CurrentUserContextTracker mUserContextTracker;
+    private final UserContextProvider mUserContextTracker;
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
             UiEventLogger uiEventLogger, NotificationManager notificationManager,
-            CurrentUserContextTracker userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
+            UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
         mController = controller;
         mLongExecutor = executor;
         mUiEventLogger = uiEventLogger;
@@ -122,7 +120,7 @@
         String action = intent.getAction();
         Log.d(TAG, "onStartCommand " + action);
 
-        int mCurrentUserId = mUserContextTracker.getCurrentUserContext().getUserId();
+        int mCurrentUserId = mUserContextTracker.getUserContext().getUserId();
         UserHandle currentUser = new UserHandle(mCurrentUserId);
         switch (action) {
             case ACTION_START:
@@ -138,7 +136,7 @@
                 setTapsVisible(mShowTaps);
 
                 mRecorder = new ScreenMediaRecorder(
-                        mUserContextTracker.getCurrentUserContext(),
+                        mUserContextTracker.getUserContext(),
                         mCurrentUserId,
                         mAudioSource,
                         this
@@ -158,7 +156,7 @@
                 // we want to post the notifications for that user, which is NOT current user
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userId == -1) {
-                    userId = mUserContextTracker.getCurrentUserContext().getUserId();
+                    userId = mUserContextTracker.getUserContext().getUserId();
                 }
                 Log.d(TAG, "notifying for user " + userId);
                 stopRecording(userId);
@@ -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/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index dc47ab4..2b62a29 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -35,7 +35,7 @@
 import android.widget.Switch;
 
 import com.android.systemui.R;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -51,7 +51,7 @@
     private static final String TAG = "ScreenRecordDialog";
 
     private final RecordingController mController;
-    private final CurrentUserContextTracker mCurrentUserContextTracker;
+    private final UserContextProvider mUserContextProvider;
     private Switch mTapsSwitch;
     private Switch mAudioSwitch;
     private Spinner mOptions;
@@ -59,9 +59,9 @@
 
     @Inject
     public ScreenRecordDialog(RecordingController controller,
-            CurrentUserContextTracker currentUserContextTracker) {
+            UserContextProvider userContextProvider) {
         mController = controller;
-        mCurrentUserContextTracker = currentUserContextTracker;
+        mUserContextProvider = userContextProvider;
     }
 
     @Override
@@ -108,7 +108,7 @@
     }
 
     private void requestScreenCapture() {
-        Context userContext = mCurrentUserContextTracker.getCurrentUserContext();
+        Context userContext = mUserContextProvider.getUserContext();
         boolean showTaps = mTapsSwitch.isChecked();
         ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
                 ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 6747281..7dd4edd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -81,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;
 
@@ -89,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 {
 
     /**
@@ -573,7 +573,12 @@
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
             Insets screenInsets, boolean showFlash) {
-        dismissScreenshot("new screenshot requested", true);
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            if (!mDismissAnimation.isRunning()) { // if we didn't already dismiss for another reason
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
+            }
+            dismissScreenshot("new screenshot requested", true);
+        }
 
         mScreenBitmap = screenshot;
 
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..74e0229 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")
@@ -56,7 +59,9 @@
     @UiEvent(doc = "screenshot interaction timed out")
     SCREENSHOT_INTERACTION_TIMEOUT(310),
     @UiEvent(doc = "screenshot explicitly dismissed")
-    SCREENSHOT_EXPLICIT_DISMISSAL(311);
+    SCREENSHOT_EXPLICIT_DISMISSAL(311),
+    @UiEvent(doc = "screenshot reentered for new screenshot")
+    SCREENSHOT_REENTERED(640);
 
     private final int mId;
 
@@ -81,6 +86,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/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/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
deleted file mode 100644
index d7c4caaa..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ /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.settings
-
-import android.content.ContentResolver
-import android.content.Context
-import android.os.UserHandle
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.util.Assert
-import java.lang.IllegalStateException
-
-/**
- * Tracks a reference to the context for the current user
- *
- * Constructor is injected at SettingsModule
- */
-class CurrentUserContextTracker internal constructor(
-    private val sysuiContext: Context,
-    broadcastDispatcher: BroadcastDispatcher
-) : CurrentUserContentResolverProvider {
-    private val userTracker: CurrentUserTracker
-    private var initialized = false
-
-    private var _curUserContext: Context? = null
-    val currentUserContext: Context
-        get() {
-            if (!initialized) {
-                throw IllegalStateException("Must initialize before getting context")
-            }
-            return _curUserContext!!
-        }
-
-    override val currentUserContentResolver: ContentResolver
-        get() = currentUserContext.contentResolver
-
-    init {
-        userTracker = object : CurrentUserTracker(broadcastDispatcher) {
-            override fun onUserSwitched(newUserId: Int) {
-                handleUserSwitched(newUserId)
-            }
-        }
-    }
-
-    fun initialize() {
-        initialized = true
-        userTracker.startTracking()
-        _curUserContext = makeUserContext(userTracker.currentUserId)
-    }
-
-    @VisibleForTesting
-    fun handleUserSwitched(newUserId: Int) {
-        _curUserContext = makeUserContext(newUserId)
-    }
-
-    private fun makeUserContext(uid: Int): Context {
-        Assert.isMainThread()
-        return sysuiContext.createContextAsUser(UserHandle.of(uid), 0)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
rename to packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt
index 9d05843..e0c0c15 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt
@@ -18,7 +18,10 @@
 
 import android.content.ContentResolver
 
-interface CurrentUserContentResolverProvider {
+/**
+ * Implemented by [UserTrackerImpl].
+ */
+interface UserContentResolverProvider {
 
-    val currentUserContentResolver: ContentResolver
+    val userContentResolver: ContentResolver
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
similarity index 82%
copy from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
copy to packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
index 9d05843..27af152 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.settings
 
-import android.content.ContentResolver
+import android.content.Context
 
-interface CurrentUserContentResolverProvider {
-
-    val currentUserContentResolver: ContentResolver
+/**
+ * Implemented by [UserTrackerImpl].
+ */
+interface UserContextProvider {
+    val userContext: Context
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
new file mode 100644
index 0000000..26d408f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import java.util.concurrent.Executor
+
+/**
+ * User tracker for SystemUI.
+ *
+ * This tracker provides async access to current user information, as well as callbacks for
+ * user/profile change.
+ */
+interface UserTracker : UserContentResolverProvider, UserContextProvider {
+
+    /**
+     * Current user's id.
+     */
+    val userId: Int
+
+    /**
+     * [UserHandle] for current user
+     */
+    val userHandle: UserHandle
+
+    /**
+     * List of profiles associated with the current user.
+     */
+    val userProfiles: List<UserInfo>
+
+    /**
+     * Add a [Callback] to be notified of chances, on a particular [Executor]
+     */
+    fun addCallback(callback: Callback, executor: Executor)
+
+    /**
+     * Remove a [Callback] previously added.
+     */
+    fun removeCallback(callback: Callback)
+
+    /**
+     * Ćallback for notifying of changes.
+     */
+    interface Callback {
+
+        /**
+         * Notifies that the current user has changed.
+         */
+        @JvmDefault
+        fun onUserChanged(newUser: Int, userContext: Context) {}
+
+        /**
+         * Notifies that the current user's profiles have changed.
+         */
+        @JvmDefault
+        fun onProfilesChanged(profiles: List<UserInfo>) {}
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
new file mode 100644
index 0000000..4cc0eee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.BroadcastReceiver
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import androidx.annotation.GuardedBy
+import androidx.annotation.WorkerThread
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.Assert
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.lang.IllegalStateException
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/**
+ * SystemUI cache for keeping track of the current user and associated values.
+ *
+ * The values provided asynchronously are NOT copies, but shared among all requesters. Do not
+ * modify them.
+ *
+ * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as
+ * soon as possible (and reduce its dependency graph).
+ * Other classes that want to listen to the broadcasts listened here SHOULD
+ * subscribe to this class instead.
+ *
+ * @see UserTracker
+ *
+ * Class constructed and initialized in [SettingsModule].
+ */
+class UserTrackerImpl internal constructor(
+    private val context: Context,
+    private val userManager: UserManager,
+    private val dumpManager: DumpManager,
+    private val backgroundHandler: Handler
+) : UserTracker, Dumpable, BroadcastReceiver() {
+
+    companion object {
+        private const val TAG = "UserTrackerImpl"
+    }
+
+    var initialized = false
+        private set
+
+    private val mutex = Any()
+
+    override var userId: Int by SynchronizedDelegate(context.userId)
+        private set
+
+    override var userHandle: UserHandle by SynchronizedDelegate(context.user)
+        private set
+
+    override var userContext: Context by SynchronizedDelegate(context)
+        private set
+
+    override val userContentResolver: ContentResolver
+        get() = userContext.contentResolver
+
+    /**
+     * Returns a [List<UserInfo>] of all profiles associated with the current user.
+     *
+     * The list returned is not a copy, so a copy should be made if its elements need to be
+     * modified.
+     */
+    override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
+        private set
+
+    @GuardedBy("callbacks")
+    private val callbacks: MutableList<DataItem> = ArrayList()
+
+    fun initialize(startingUser: Int) {
+        if (initialized) {
+            return
+        }
+        initialized = true
+        setUserIdInternal(startingUser)
+
+        val filter = IntentFilter().apply {
+            addAction(Intent.ACTION_USER_SWITCHED)
+            addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+            addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+        }
+        context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler)
+
+        dumpManager.registerDumpable(TAG, this)
+    }
+
+    override fun onReceive(context: Context, intent: Intent) {
+        when (intent.action) {
+            Intent.ACTION_USER_SWITCHED -> {
+                handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
+            }
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> {
+                handleProfilesChanged()
+            }
+        }
+    }
+
+    private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
+        val profiles = userManager.getProfiles(user)
+        val handle = UserHandle(user)
+        val ctx = context.createContextAsUser(handle, 0)
+
+        synchronized(mutex) {
+            userId = user
+            userHandle = handle
+            userContext = ctx
+            userProfiles = profiles.map { UserInfo(it) }
+        }
+        return ctx to profiles
+    }
+
+    @WorkerThread
+    private fun handleSwitchUser(newUser: Int) {
+        Assert.isNotMainThread()
+        if (newUser == UserHandle.USER_NULL) {
+            Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
+            return
+        }
+
+        if (newUser == userId) return
+        Log.i(TAG, "Switching to user $newUser")
+
+        val (ctx, profiles) = setUserIdInternal(newUser)
+
+        notifySubscribers {
+            onUserChanged(newUser, ctx)
+            onProfilesChanged(profiles)
+        }
+    }
+
+    @WorkerThread
+    private fun handleProfilesChanged() {
+        Assert.isNotMainThread()
+
+        val profiles = userManager.getProfiles(userId)
+        synchronized(mutex) {
+            userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy
+        }
+        notifySubscribers {
+            onProfilesChanged(profiles)
+        }
+    }
+
+    override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
+        synchronized(callbacks) {
+            callbacks.add(DataItem(WeakReference(callback), executor))
+        }
+    }
+
+    override fun removeCallback(callback: UserTracker.Callback) {
+        synchronized(callbacks) {
+            callbacks.removeIf { it.sameOrEmpty(callback) }
+        }
+    }
+
+    private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
+        val list = synchronized(callbacks) {
+            callbacks.toList()
+        }
+        list.forEach {
+            if (it.callback.get() != null) {
+                it.executor.execute {
+                    it.callback.get()?.action()
+                }
+            }
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("Initialized: $initialized")
+        if (initialized) {
+            pw.println("userId: $userId")
+            val ids = userProfiles.map { it.id }
+            pw.println("userProfiles: $ids")
+        }
+        val list = synchronized(callbacks) {
+            callbacks.toList()
+        }
+        pw.println("Callbacks:")
+        list.forEach {
+            it.callback.get()?.let {
+                pw.println("  $it")
+            }
+        }
+    }
+
+    private class SynchronizedDelegate<T : Any>(
+        private var value: T
+    ) : ReadWriteProperty<UserTrackerImpl, T> {
+
+        @GuardedBy("mutex")
+        override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T {
+            if (!thisRef.initialized) {
+                throw IllegalStateException("Must initialize before getting ${property.name}")
+            }
+            return synchronized(thisRef.mutex) { value }
+        }
+
+        @GuardedBy("mutex")
+        override fun setValue(thisRef: UserTrackerImpl, property: KProperty<*>, value: T) {
+            synchronized(thisRef.mutex) { this.value = value }
+        }
+    }
+}
+
+private data class DataItem(
+    val callback: WeakReference<UserTracker.Callback>,
+    val executor: Executor
+) {
+    fun sameOrEmpty(other: UserTracker.Callback): Boolean {
+        return callback.get()?.equals(other) ?: true
+    }
+}
\ No newline at end of file
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 eb5bd5c..7084d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -16,13 +16,18 @@
 
 package com.android.systemui.settings.dagger;
 
+import android.app.ActivityManager;
 import android.content.Context;
+import android.os.Handler;
+import android.os.UserManager;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.settings.CurrentUserContentResolverProvider;
-import com.android.systemui.settings.CurrentUserContextTracker;
-
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserContentResolverProvider;
+import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.settings.UserTrackerImpl;
 
 import dagger.Binds;
 import dagger.Module;
@@ -34,22 +39,27 @@
 @Module
 public abstract class SettingsModule {
 
-    /**
-     * Provides and initializes a CurrentUserContextTracker
-     */
-    @Singleton
-    @Provides
-    static CurrentUserContextTracker provideCurrentUserContextTracker(
-            Context context,
-            BroadcastDispatcher broadcastDispatcher) {
-        CurrentUserContextTracker tracker =
-                new CurrentUserContextTracker(context, broadcastDispatcher);
-        tracker.initialize();
-        return tracker;
-    }
 
     @Binds
-    @Singleton
-    abstract CurrentUserContentResolverProvider bindCurrentUserContentResolverTracker(
-            CurrentUserContextTracker tracker);
+    @SysUISingleton
+    abstract UserContextProvider bindUserContextProvider(UserTracker tracker);
+
+    @Binds
+    @SysUISingleton
+    abstract UserContentResolverProvider bindUserContentResolverProvider(
+            UserTracker tracker);
+
+    @SysUISingleton
+    @Provides
+    static UserTracker provideUserTracker(
+            Context context,
+            UserManager userManager,
+            DumpManager dumpManager,
+            @Background Handler handler
+    ) {
+        int startingUser = ActivityManager.getCurrentUser();
+        UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler);
+        tracker.initialize(startingUser);
+        return 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..0d92d1e 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.SplitScreen;
+
+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<SplitScreen> mSplitScreenOptional;
     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<SplitScreen> splitScreenOptional, Recents recents) {
         super(context);
-        mDivider = divider;
+        mSplitScreenOptional = splitScreenOptional;
         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 (mSplitScreenOptional.isPresent()) {
+            SplitScreen splitScreen = mSplitScreenOptional.get();
+            if (splitScreen.isDividerVisible()) {
+                // If there is already a docked window, we respond by resizing the docking pane.
+                DividerView dividerView = splitScreen.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 d40b666..0000000
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ /dev/null
@@ -1,158 +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 onActivityForcedResizable(String packageName, int taskId,
-                            int reason) {
-                        mDividerController.onActivityForcedResizable(packageName, taskId, reason);
-                    }
-
-                    @Override
-                    public void onActivityDismissingDockedStack() {
-                        mDividerController.onActivityDismissingSplitScreen();
-                    }
-
-                    @Override
-                    public void onActivityLaunchOnSecondaryDisplayFailed() {
-                        mDividerController.onActivityLaunchOnSecondaryDisplayFailed();
-                    }
-                }
-        );
-    }
-
-    @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..fdf24b1 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -62,10 +62,8 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
 import java.util.function.Consumer;
@@ -76,7 +74,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();
@@ -138,6 +136,7 @@
     private final Rect mOtherInsetRect = new Rect();
     private final Rect mLastResizeRect = new Rect();
     private final Rect mTmpRect = new Rect();
+    private SplitScreenController mSplitScreenController;
     private WindowManagerProxy mWindowManagerProxy;
     private DividerWindowManager mWindowManager;
     private VelocityTracker mVelocityTracker;
@@ -353,9 +352,11 @@
         }
     }
 
-    public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
+    void injectDependencies(SplitScreenController splitScreenController,
+            DividerWindowManager windowManager, DividerState dividerState,
             DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
             DividerImeController imeController, WindowManagerProxy wmProxy) {
+        mSplitScreenController = splitScreenController;
         mWindowManager = windowManager;
         mState = dividerState;
         mCallback = callback;
@@ -697,8 +698,7 @@
                 mTmpRect.top = 0;
                 break;
         }
-        Dependency.get(OverviewProxyService.class)
-                .notifySplitScreenBoundsChanged(mOtherTaskRect, mTmpRect);
+        mSplitScreenController.notifyBoundsChanged(mOtherTaskRect, mTmpRect);
     }
 
     private void cancelFlingAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index ff8bab0..4c26694 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -69,9 +69,9 @@
     }
 
     public ForcedResizableInfoActivityController(Context context,
-            DividerController dividerController) {
+            SplitScreenController splitScreenController) {
         mContext = context;
-        dividerController.registerInSplitScreenListener(mDockedStackExistsListener);
+        splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreen.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreen.java
new file mode 100644
index 0000000..93b1f86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreen.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.systemui.stackdivider;
+
+import android.graphics.Rect;
+import android.window.WindowContainerToken;
+
+import java.io.PrintWriter;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Interface to engage split screen feature.
+ */
+public interface SplitScreen {
+    /** Returns {@code true} if split screen is supported on the device. */
+    boolean isSplitScreenSupported();
+
+    /** Called when keyguard showing state changed. */
+    void onKeyguardVisibilityChanged(boolean isShowing);
+
+    /** Returns {@link DividerView}. */
+    DividerView getDividerView();
+
+    /** Returns {@code true} if one of the split screen is in minimized mode. */
+    boolean isMinimized();
+
+    /** Returns {@code true} if the home stack is resizable. */
+    boolean isHomeStackResizable();
+
+    /** Returns {@code true} if the divider is visible. */
+    boolean isDividerVisible();
+
+    /** Switch to minimized state if appropriate. */
+    void setMinimized(boolean minimized);
+
+    /**
+     * Workaround for b/62528361, at the time recents has drawn, it may happen before a
+     * configuration change to the Divider, and internally, the event will be posted to the
+     * subscriber, or DividerView, which has been removed and prevented from resizing. Instead,
+     * register the event handler here and proxy the event to the current DividerView.
+     */
+    void onRecentsDrawn();
+
+    /** Called when there's an activity forced resizable. */
+    void onActivityForcedResizable(String packageName, int taskId, int reason);
+
+    /** Called when there's an activity dismissing split screen. */
+    void onActivityDismissingSplitScreen();
+
+    /** Called when there's an activity launch on secondary display failed. */
+    void onActivityLaunchOnSecondaryDisplayFailed();
+
+    /** Called when there's a task undocking. */
+    void onUndockingTask();
+
+    /** Called when the first docked animation frame rendered. */
+    void onDockedFirstAnimationFrame();
+
+    /** Called when top task docked. */
+    void onDockedTopTask();
+
+    /** Called when app transition finished. */
+    void onAppTransitionFinished();
+
+    /** Dumps current status of Split Screen. */
+    void dump(PrintWriter pw);
+
+    /** Registers listener that gets called whenever the existence of the divider changes. */
+    void registerInSplitScreenListener(Consumer<Boolean> listener);
+
+    /** Registers listener that gets called whenever the split screen bounds changes. */
+    void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener);
+
+    /** @return the container token for the secondary split root task. */
+    WindowContainerToken getSecondaryRoot();
+}
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 1ee8abb..360b495 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;
@@ -45,29 +46,40 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
- * Controls the docked stack divider.
+ * Controls split screen feature.
  */
-public class DividerController implements DisplayController.OnDisplaysChangedListener {
+public class SplitScreenController implements SplitScreen,
+        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 final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
+            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;
@@ -77,21 +89,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;
@@ -100,7 +107,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) -> {
@@ -140,10 +147,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.
@@ -155,15 +159,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
@@ -256,8 +260,8 @@
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
         DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
-        mView.injectDependencies(mWindowManager, mDividerState, mForcedResizableController, mSplits,
-                mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
+        mView.injectDependencies(this, 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(
@@ -465,6 +469,24 @@
         }
     }
 
+    @Override
+    public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+        synchronized (mBoundsChangedListeners) {
+            mBoundsChangedListeners.add(new WeakReference<>(listener));
+        }
+    }
+
+    /** Notifies the bounds of split screen changed. */
+    void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+        synchronized (mBoundsChangedListeners) {
+            mBoundsChangedListeners.removeIf(wf -> {
+                BiConsumer<Rect, Rect> l = wf.get();
+                if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets);
+                return l == null;
+            });
+        }
+    }
+
     void startEnterSplit() {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
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/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index eca4c80..0df69a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -185,6 +185,7 @@
         mAlertEntries.put(entry.getKey(), alertEntry);
         onAlertEntryAdded(alertEntry);
         entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        entry.setIsAlerting(true);
     }
 
     /**
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/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 4fa7822..e61e05a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -163,7 +165,7 @@
         if (!mDragDownCallback.isFalsingCheckNeeded()) {
             return false;
         }
-        return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
+        return mFalsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN) || !mDraggedFarEnough;
     }
 
     private void captureStartingChild(float x, float y) {
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/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/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 1079f10..17bf346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -55,14 +55,6 @@
     void updateNotificationViews(String reason);
 
     /**
-     * Returns the maximum number of notifications to show while locked.
-     *
-     * @param recompute whether something has changed that means we should recompute this value
-     * @return the maximum number of notifications to show while locked
-     */
-    int getMaxNotificationsWhileLocked(boolean recompute);
-
-    /**
      * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
      */
     void onUpdateRowStates();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index cdf1f10..c1196d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
 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
@@ -44,14 +45,13 @@
 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
index 1fd0b03..24515f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -164,6 +164,13 @@
     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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 77abcfa..1e935c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -85,6 +85,10 @@
         return mView.getShelfIcons();
     }
 
+    public @View.Visibility int getVisibility() {
+        return mView.getVisibility();
+    };
+
     public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
         mView.setCollapsedIcons(notificationIcons);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index f272be4..852c055 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;
@@ -421,11 +420,6 @@
 
         int visibleNotifications = 0;
         boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-        int maxNotifications = -1;
-        if (onKeyguard && !mBypassController.getBypassEnabled()) {
-            maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
-        }
-        mListContainer.setMaxDisplayedNotifications(maxNotifications);
         Stack<ExpandableNotificationRow> stack = new Stack<>();
         for (int i = N - 1; i >= 0; i--) {
             View child = mListContainer.getContainerChildAt(i);
@@ -440,8 +434,6 @@
             boolean isChildNotification =
                     mGroupManager.isChildInGroupWithSummary(entry.getSbn());
 
-            row.setOnKeyguard(onKeyguard);
-
             if (!onKeyguard) {
                 // If mAlwaysExpandNonGroupedNotification is false, then only expand the
                 // very first notification and if it's not a child of grouped notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index ff13c4e..6fa3633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
+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
@@ -41,13 +43,12 @@
 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,
@@ -106,7 +107,7 @@
     private var velocityTracker: VelocityTracker? = null
 
     private val isFalseTouch: Boolean
-        get() = falsingManager.isFalseTouch
+        get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
     var qsExpanded: Boolean = false
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index f93078f..e944249 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -30,6 +30,7 @@
 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;
@@ -40,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";
@@ -103,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;
@@ -190,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);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 27e4ade..1ec043c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -21,6 +21,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+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;
@@ -30,13 +31,12 @@
 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;
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 991b79a..db2875a3 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;
@@ -36,23 +37,29 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 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.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 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.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -65,7 +72,7 @@
 @Module
 public interface StatusBarDependenciesModule {
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationRemoteInputManager provideNotificationRemoteInputManager(
             Context context,
@@ -92,7 +99,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationMediaManager provideNotificationMediaManager(
             Context context,
@@ -117,7 +124,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationListener provideNotificationListener(
             Context context,
@@ -128,7 +135,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static SmartReplyController provideSmartReplyController(
             NotificationEntryManager entryManager,
@@ -137,8 +144,14 @@
         return new SmartReplyController(entryManager, statusBarService, clickNotifier);
     }
 
+
     /** */
-    @Singleton
+    @Binds
+    NotificationRemoteInputManager.Callback provideNotificationRemoteInputManagerCallback(
+            StatusBarRemoteInputCallback callbackImpl);
+
+    /** */
+    @SysUISingleton
     @Provides
     static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
             Context context,
@@ -176,8 +189,26 @@
      * Provides our instance of CommandQueue which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
         return new CommandQueue(context, protoTracer);
     }
+
+    /**
+     */
+    @Binds
+    ManagedProfileController provideManagedProfileController(
+            ManagedProfileControllerImpl controllerImpl);
+
+    /**
+     */
+    @Binds
+    SysuiStatusBarStateController providesSysuiStatusBarStateController(
+            StatusBarStateControllerImpl statusBarStateControllerImpl);
+
+    /**
+     */
+    @Binds
+    StatusBarIconController provideStatusBarIconController(
+            StatusBarIconControllerImpl controllerImpl);
 }
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 b813b62..87a3f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -30,10 +30,10 @@
 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.
@@ -41,7 +41,7 @@
  * Flags protect any changes from being shown. Notifications that are adjusted by the assistant
  * should show an indicator.
  */
-@Singleton
+@SysUISingleton
 public class AssistantFeedbackController extends ContentObserver {
     private final Uri FEEDBACK_URI
             = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
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..838cf0c 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.SplitScreen;
 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,13 @@
     private final CommandQueue mCommandQueue;
     private boolean mDockedStackExists;
     private KeyguardStateController mKeyguardStateController;
-    private final Divider mDivider;
+    private final Optional<SplitScreen> mSplitScreenOptional;
 
     @Inject
     public InstantAppNotifier(Context context, CommandQueue commandQueue,
-            @UiBackground Executor uiBgExecutor, Divider divider) {
+            @UiBackground Executor uiBgExecutor, Optional<SplitScreen> splitScreenOptional) {
         super(context);
-        mDivider = divider;
+        mSplitScreenOptional = splitScreenOptional;
         mCommandQueue = commandQueue;
         mUiBgExecutor = uiBgExecutor;
     }
@@ -105,11 +106,11 @@
         mCommandQueue.addCallback(this);
         mKeyguardStateController.addCallback(this);
 
-        mDivider.registerInSplitScreenListener(
-                exists -> {
+        mSplitScreenOptional.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 e1ff8724..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 0469176..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,6 +19,7 @@
 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
@@ -26,16 +27,13 @@
 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,
@@ -99,7 +97,6 @@
         }
 
     private var collapsedEnoughToHide: Boolean = false
-    lateinit var iconAreaController: NotificationIconAreaController
 
     var pulsing: Boolean = false
         set(value) {
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/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 387247e..8ce9d94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -177,6 +177,7 @@
     @Nullable private Long mPendingAnimationDuration;
     private boolean mIsMarkedForUserTriggeredMovement;
     private boolean mShelfIconVisible;
+    private boolean mIsAlerting;
 
     /**
      * @param sbn the StatusBarNotification from system server
@@ -955,6 +956,14 @@
         mIsMarkedForUserTriggeredMovement = marked;
     }
 
+    public void setIsAlerting(boolean isAlerting) {
+        mIsAlerting = isAlerting;
+    }
+
+    public boolean isAlerting() {
+        return mIsAlerting;
+    }
+
     /** Information about a suggestion that is being edited. */
     public static class EditedSuggestionInfo {
 
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 2f12088..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";
 
@@ -87,8 +83,8 @@
 
     }
 
-    public NotifSection getSection() {
-        return mNotifSection;
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
     }
 
     /**
@@ -183,7 +179,7 @@
     /**
      * 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();
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 95ba759..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";
 
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 3e11067..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";
 
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 f90ec0b..e5425cf 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";
@@ -140,7 +140,7 @@
                                         .build();
                         ExpandableNotificationRowController rowController =
                                 component.getExpandableNotificationRowController();
-                        rowController.init();
+                        rowController.init(entry);
                         entry.setRowController(rowController);
                         bindRow(entry, row);
                         updateRow(entry, row);
@@ -160,7 +160,6 @@
         mNotificationRemoteInputManager.bindRow(row);
         row.setOnActivatedListener(mPresenter);
         entry.setRow(row);
-        row.setEntry(entry);
         mNotifBindPipeline.manageRow(entry, row);
         mBindRowCallback.onBindRow(row);
     }
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 1c02c62..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;
@@ -36,12 +37,11 @@
 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;
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/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index 114c30e..c09122e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -14,17 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface PipMenuActivityClass {
+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/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index 00fd09d..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,16 +17,16 @@
 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.ExpandableNotificationRowController
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * 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, ExpandableNotificationRowController>()
 
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
index e812494..a1800ed 100644
--- 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
@@ -25,10 +25,10 @@
  * we should just modify NLC to implement the NodeController interface.
  */
 class RootNodeController(
-    private val listContainer: NotificationListContainer
+    private val listContainer: NotificationListContainer,
+    override val view: View
 ) : NodeController {
     override val nodeLabel: String = "<root>"
-    override val view: View = listContainer as View
 
     override fun getChildAt(index: Int): View? {
         return listContainer.getContainerChildAt(index)
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
index 201be59..3c35b7bd 100644
--- 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
@@ -16,11 +16,14 @@
 
 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
 
@@ -29,11 +32,15 @@
  * currently populate the notification shade.
  */
 class ShadeViewManager constructor(
+    context: Context,
     listContainer: NotificationListContainer,
     logger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
 ) {
-    private val rootController = RootNodeController(listContainer)
+    // 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) {
@@ -52,6 +59,7 @@
             root.children.add(buildNotifNode(entry, root))
         }
 
+        notificationIconAreaController.updateNotificationIcons(notifList)
         return root
     }
 
@@ -79,10 +87,17 @@
 }
 
 class ShadeViewManagerFactory @Inject constructor(
+    private val context: Context,
     private val logger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
 ) {
     fun create(listContainer: NotificationListContainer): ShadeViewManager {
-        return ShadeViewManager(listContainer, logger, viewBarn)
+        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..f3ed95b 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,11 +28,13 @@
 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.settings.UserContextProvider;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -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,
@@ -125,14 +127,14 @@
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
-            CurrentUserContextTracker contextTracker,
+            UserContextProvider contextTracker,
             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 7f42fe04..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/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 d7fa54f..46b4973 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;
@@ -141,7 +141,7 @@
         void onExpansionChanged(boolean isExpanded);
     }
 
-    private StatusBarStateController mStatusbarStateController;
+    private StatusBarStateController mStatusBarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
     private RowContentBindStage mRowContentBindStage;
@@ -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;
@@ -463,16 +463,6 @@
     }
 
     /**
-     * Set the entry for the row.
-     *
-     * @param entry the entry this row is tied to
-     */
-    public void setEntry(@NonNull NotificationEntry entry) {
-        mEntry = entry;
-        cacheIsSystemNotification();
-    }
-
-    /**
      * Marks a content view as freeable, setting it so that future inflations do not reinflate
      * and ensuring that the view is freed when it is safe to remove.
      *
@@ -1445,8 +1435,8 @@
         }
         dismiss(fromAccessibility);
         if (mEntry.isClearable()) {
-            if (mOnDismissCallback != null) {
-                mOnDismissCallback.onDismiss(mEntry, REASON_CANCEL);
+            if (mOnUserInteractionCallback != null) {
+                mOnUserInteractionCallback.onDismiss(mEntry, REASON_CANCEL);
             }
         }
     }
@@ -1463,10 +1453,6 @@
         return mIsBlockingHelperShowing && mNotificationTranslationFinished;
     }
 
-    void setOnDismissCallback(OnDismissCallback onDismissCallback) {
-        mOnDismissCallback = onDismissCallback;
-    }
-
     @Override
     public View getShelfTransformationTarget() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -1588,6 +1574,7 @@
      * Initialize row.
      */
     public void initialize(
+            NotificationEntry entry,
             String appName,
             String notificationKey,
             ExpansionLogger logger,
@@ -1600,7 +1587,9 @@
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
             StatusBarStateController statusBarStateController,
-            PeopleNotificationIdentifier peopleNotificationIdentifier) {
+            PeopleNotificationIdentifier peopleNotificationIdentifier,
+            OnUserInteractionCallback onUserInteractionCallback) {
+        mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
             mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
@@ -1619,11 +1608,15 @@
         mMediaManager = notificationMediaManager;
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
-        mStatusbarStateController = statusBarStateController;
+        mStatusBarStateController = statusBarStateController;
+
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
         for (NotificationContentView l : mLayouts) {
             l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);
         }
+        mOnUserInteractionCallback = onUserInteractionCallback;
+
+        cacheIsSystemNotification();
     }
 
     private void initDimens() {
@@ -2136,8 +2129,8 @@
      */
     @Override
     public boolean isSoundEffectsEnabled() {
-        final boolean mute = mStatusbarStateController != null
-                && mStatusbarStateController.isDozing()
+        final boolean mute = mStatusBarStateController != null
+                && mStatusBarStateController.isDozing()
                 && mSecureStateProvider != null &&
                 !mSecureStateProvider.getAsBoolean();
         return !mute && super.isSoundEffectsEnabled();
@@ -2261,10 +2254,7 @@
         }
     }
 
-    /**
-     * @param onKeyguard whether to prevent notification expansion
-     */
-    public void setOnKeyguard(boolean onKeyguard) {
+    void setOnKeyguard(boolean onKeyguard) {
         if (onKeyguard != mOnKeyguard) {
             boolean wasAboveShelf = isAboveShelf();
             final boolean wasExpanded = isExpanded();
@@ -2333,7 +2323,7 @@
     }
 
     private boolean isDozing() {
-        return mStatusbarStateController != null && mStatusbarStateController.isDozing();
+        return mStatusBarStateController != null && mStatusBarStateController.isDozing();
     }
 
     @Override
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 690dce9..ce760cb 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
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -29,6 +30,7 @@
 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.NotificationEntry;
 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;
@@ -71,7 +73,7 @@
             this::logNotificationExpansion;
     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;
@@ -90,7 +92,7 @@
             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;
@@ -108,7 +110,7 @@
         mOnExpandClickListener = onExpandClickListener;
         mStatusBarStateController = statusBarStateController;
         mNotificationGutsManager = notificationGutsManager;
-        mOnDismissCallback = onDismissCallback;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
         mFalsingManager = falsingManager;
@@ -118,9 +120,10 @@
     /**
      * Initialize the controller.
      */
-    public void init() {
+    public void init(NotificationEntry entry) {
         mActivatableNotificationViewController.init();
         mView.initialize(
+                entry,
                 mAppName,
                 mNotificationKey,
                 mExpansionLogger,
@@ -133,9 +136,17 @@
                 mOnFeedbackClickListener,
                 mFalsingManager,
                 mStatusBarStateController,
-                mPeopleNotificationIdentifier
+                mPeopleNotificationIdentifier,
+                mOnUserInteractionCallback
+
         );
-        mView.setOnDismissCallback(mOnDismissCallback);
+        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+            @Override
+            public void onStateChanged(int newState) {
+                mView.setOnKeyguard(newState == KEYGUARD);
+            }
+        });
+        mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
             mView.setLongPressListener((v, x, y, item) -> {
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/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 05d44f6..7d418f3 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
@@ -52,7 +52,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -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 =
@@ -122,28 +121,30 @@
     private final INotificationManager mNotificationManager;
     private final LauncherApps mLauncherApps;
     private final ShortcutManager mShortcutManager;
-    private final CurrentUserContextTracker mContextTracker;
+    private final UserContextProvider mContextTracker;
     private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
     private final UiEventLogger mUiEventLogger;
 
     /**
      * 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,
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
-            CurrentUserContextTracker contextTracker,
+            UserContextProvider contextTracker,
             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,
@@ -363,7 +365,7 @@
         notificationInfoView.bindNotification(
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 packageName,
                 row.getEntry().getChannel(),
@@ -474,7 +476,7 @@
                 mShortcutManager,
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 packageName,
                 entry.getChannel(),
                 entry,
@@ -482,7 +484,7 @@
                 onSettingsClick,
                 onSnoozeClickListener,
                 iconFactoryLoader,
-                mContextTracker.getCurrentUserContext(),
+                mContextTracker.getUserContext(),
                 mBuilderProvider,
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mMainHandler,
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/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/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 11e698b..0302b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -42,7 +42,7 @@
     private static final float MAX_PULSE_HEIGHT = 100000f;
 
     private final SectionProvider mSectionProvider;
-    private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
+    private ArrayList<View> mDraggedViews = new ArrayList<>();
     private int mScrollY;
     private int mAnchorViewIndex;
     private int mAnchorViewY;
@@ -81,17 +81,17 @@
     private boolean mAppearing;
     private float mPulseHeight = MAX_PULSE_HEIGHT;
     private float mDozeAmount = 0.0f;
-    private HeadsUpManager mHeadUpManager;
     private Runnable mOnPulseHeightChangedListener;
     private ExpandableNotificationRow mTrackedHeadsUpRow;
     private float mAppearFraction;
 
+    /** Tracks the state from AlertingNotificationManager#hasNotifications() */
+    private boolean mHasAlertEntries;
+
     public AmbientState(
             Context context,
-            @NonNull SectionProvider sectionProvider,
-            HeadsUpManager headsUpManager) {
+            @NonNull SectionProvider sectionProvider) {
         mSectionProvider = sectionProvider;
-        mHeadUpManager = headsUpManager;
         reload(context);
     }
 
@@ -164,7 +164,7 @@
     }
 
     /** Call when dragging begins. */
-    public void onBeginDrag(ExpandableView view) {
+    public void onBeginDrag(View view) {
         mDraggedViews.add(view);
     }
 
@@ -172,7 +172,7 @@
         mDraggedViews.remove(view);
     }
 
-    public ArrayList<ExpandableView> getDraggedViews() {
+    public ArrayList<View> getDraggedViews() {
         return mDraggedViews;
     }
 
@@ -393,7 +393,7 @@
     }
 
     public boolean hasPulsingNotifications() {
-        return mPulsing && mHeadUpManager != null && mHeadUpManager.hasNotifications();
+        return mPulsing && mHasAlertEntries;
     }
 
     public void setPulsing(boolean hasPulsing) {
@@ -408,10 +408,7 @@
     }
 
     public boolean isPulsing(NotificationEntry entry) {
-        if (!mPulsing || mHeadUpManager == null) {
-            return false;
-        }
-        return mHeadUpManager.isAlerting(entry.getKey());
+        return mPulsing && entry.isAlerting();
     }
 
     public boolean isPanelTracking() {
@@ -568,4 +565,8 @@
     public float getAppearFraction() {
         return mAppearFraction;
     }
+
+    public void setHasAlertEntries(boolean hasAlertEntries) {
+        mHasAlertEntries = hasAlertEntries;
+    }
 }
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 7e4266c..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;
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 d2f8a39..43c7491 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
@@ -44,7 +44,6 @@
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -54,7 +53,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -94,26 +92,14 @@
 import com.android.systemui.ExpandHelper;
 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;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -125,10 +111,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;
@@ -138,28 +124,18 @@
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
-import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 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,8 +155,7 @@
 /**
  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
  */
-public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
-        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";
@@ -197,13 +172,12 @@
      * 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 KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final SysuiStatusBarStateController mStatusbarStateController;
-    private final KeyguardMediaController mKeyguardMediaController;
 
     private ExpandHelper mExpandHelper;
-    private final NotificationSwipeHelper mSwipeHelper;
+    private NotificationSwipeHelper mSwipeHelper;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
     private final boolean mShouldDrawNotificationBackground;
@@ -251,11 +225,12 @@
     private int mBottomMargin;
     private int mBottomInset = 0;
     private float mQsExpansionFraction;
+    private int mCurrentUserId;
 
     /**
      * The algorithm which calculates the properties for our children
      */
-    protected final StackScrollAlgorithm mStackScrollAlgorithm;
+    private final StackScrollAlgorithm mStackScrollAlgorithm;
 
     private final AmbientState mAmbientState;
     private NotificationGroupManager mGroupManager;
@@ -328,7 +303,6 @@
      * motion.
      */
     private int mMaxScrollAfterExpand;
-    private ExpandableNotificationRow.LongPressListener mLongPressListener;
     boolean mCheckForLeavebehind;
 
     /**
@@ -350,12 +324,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;
@@ -363,14 +332,10 @@
     private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
             = new HashSet<>();
-    private HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mRoundnessManager;
     private boolean mTrackingHeadsUp;
-    private ScrimController mScrimController;
     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() {
@@ -500,8 +465,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;
@@ -514,7 +477,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 =
@@ -526,7 +488,6 @@
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
     private NotificationPanelViewController mNotificationPanelController;
 
-    private final NotificationGutsManager mNotificationGutsManager;
     private final NotificationSectionsManager mSectionsManager;
     private final ForegroundServiceSectionController mFgsSectionController;
     private ForegroundServiceDungeonView mFgsSectionView;
@@ -539,11 +500,10 @@
     private int mWaterfallTopInset;
     private NotificationStackScrollLayoutController mController;
 
-    private SysuiColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
-            (colorExtractor, which) -> {
-                final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
-                updateDecorViews(useDarkText);
-            };
+    private boolean mKeyguardMediaControllorVisible;
+    private NotificationEntry mTopHeadsUpEntry;
+    private long mNumHeadsUp;
+    private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -558,20 +518,41 @@
                 }
             };
 
+    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,
             NotificationRoundnessManager notificationRoundnessManager,
             DynamicPrivacyController dynamicPrivacyController,
-            SysuiStatusBarStateController statusBarStateController,
-            HeadsUpManagerPhone headsUpManager,
-            KeyguardBypassController keyguardBypassController,
-            KeyguardMediaController keyguardMediaController,
-            FalsingManager falsingManager,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationGutsManager notificationGutsManager,
-            ZenModeController zenController,
+            SysuiStatusBarStateController statusbarStateController,
             NotificationSectionsManager notificationSectionsManager,
             ForegroundServiceSectionController fgsSectionController,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
@@ -585,14 +566,6 @@
         Resources res = getResources();
 
         mRoundnessManager = notificationRoundnessManager;
-
-        mLockscreenUserManager = notificationLockscreenUserManager;
-        mNotificationGutsManager = notificationGutsManager;
-        mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
-        mKeyguardBypassController = keyguardBypassController;
-        mFalsingManager = falsingManager;
-        mZenController = zenController;
         mFgsSectionController = fgsSectionController;
 
         mSectionsManager = notificationSectionsManager;
@@ -604,23 +577,18 @@
         });
         mSections = mSectionsManager.createSectionsForBuckets();
 
-        mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
+        mAmbientState = new AmbientState(context, mSectionsManager);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
         mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
                 minHeight, maxHeight);
         mExpandHelper.setEventSource(this);
-        mExpandHelper.setScrollAdapter(this);
-        mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
-                getContext(), mMenuEventListener, mFalsingManager);
+        mExpandHelper.setScrollAdapter(mScrollAdapter);
+
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
-        initView(context);
         mShouldDrawNotificationBackground =
                 res.getBoolean(R.bool.config_drawNotificationBackground);
-        mFadeNotificationsOnDismiss =
-                res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-        mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
         setOutlineProvider(mOutlineProvider);
 
         // Blocking helper manager wants to know the expanded state, update as well.
@@ -669,22 +637,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(
@@ -723,7 +679,7 @@
     public float getWakeUpHeight() {
         ExpandableView firstChild = getFirstChildWithBackground();
         if (firstChild != null) {
-            if (mKeyguardBypassController.getBypassEnabled()) {
+            if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
                 return firstChild.getHeadsUpHeightWithoutHeader();
             } else {
                 return firstChild.getCollapsedHeight();
@@ -732,36 +688,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() {
@@ -800,52 +733,12 @@
         return false;
     }
 
-  @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-  public RemoteInputController.Delegate createDelegate() {
-        return new RemoteInputController.Delegate() {
-            public void setRemoteInputActive(NotificationEntry entry,
-                    boolean remoteInputActive) {
-                mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
-                entry.notifyHeightChanged(true /* needsAnimation */);
-                updateFooter();
-            }
-
-            public void lockScrollTo(NotificationEntry entry) {
-                NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
-            }
-
-            public void requestDisallowLongPressAndDismiss() {
-                requestDisallowLongPress();
-                requestDisallowDismiss();
-            }
-        };
-    }
-
-    @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);
-    }
-
     @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();
@@ -929,7 +822,7 @@
             }
         }
         boolean shouldDrawBackground;
-        if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
             shouldDrawBackground = isPulseExpanding();
         } else {
             shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
@@ -1025,7 +918,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void updateBackgroundDimming() {
+    void updateBackgroundDimming() {
         // No need to update the background color if it's not being drawn.
         if (!mShouldDrawNotificationBackground) {
             return;
@@ -1044,9 +937,18 @@
         }
     }
 
+    private void reinitView() {
+        initView(getContext(), mKeyguardBypassEnabledProvider, mSwipeHelper);
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void initView(Context context) {
+    void initView(Context context,
+            KeyguardBypassEnabledProvider keyguardBypassEnabledProvider,
+            NotificationSwipeHelper swipeHelper) {
         mScroller = new OverScroller(getContext());
+        mKeyguardBypassEnabledProvider = keyguardBypassEnabledProvider;
+        mSwipeHelper = swipeHelper;
+
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setClipChildren(false);
         final ViewConfiguration configuration = ViewConfiguration.get(context);
@@ -1078,6 +980,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 */);
@@ -1249,7 +1160,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;
@@ -1309,7 +1220,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void requestChildrenUpdate() {
+    void requestChildrenUpdate() {
         if (!mChildrenUpdateRequested) {
             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
             mChildrenUpdateRequested = true;
@@ -1439,7 +1350,7 @@
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
-        if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
             appear = calculateAppearFractionBypass();
             expandAmount = getPulseHeight();
         } else {
@@ -1523,11 +1434,10 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getTopHeadsUpPinnedHeight() {
-        NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
-        if (topEntry == null) {
+        if (mTopHeadsUpEntry == null) {
             return 0;
         }
-        ExpandableNotificationRow row = topEntry.getRow();
+        ExpandableNotificationRow row = mTopHeadsUpEntry.getRow();
         if (row.isChildInGroup()) {
             final NotificationEntry groupSummary =
                     mGroupManager.getGroupSummary(row.getEntry().getSbn());
@@ -1548,7 +1458,7 @@
         int visibleNotifCount = getVisibleNotificationCount();
         if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
             if (isHeadsUpTransition()
-                    || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) {
+                    || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
                 if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) {
                     appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
                 }
@@ -1680,7 +1590,7 @@
      * @return the child at the given location.
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private ExpandableView getChildAtPosition(float touchX, float touchY,
+    ExpandableView getChildAtPosition(float touchX, float touchY,
             boolean requireMinHeight, boolean ignoreDecors) {
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
@@ -1706,9 +1616,9 @@
                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
                     NotificationEntry entry = row.getEntry();
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
-                            && mHeadsUpManager.getTopEntry().getRow() != row
+                            && mTopHeadsUpEntry.getRow() != row
                             && mGroupManager.getGroupSummary(
-                            mHeadsUpManager.getTopEntry().getSbn())
+                            mTopHeadsUpEntry.getSbn())
                             != entry) {
                         continue;
                     }
@@ -1828,7 +1738,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private boolean onKeyguard() {
+    boolean onKeyguard() {
         return mStatusBarState == StatusBarState.KEYGUARD;
     }
 
@@ -1841,7 +1751,7 @@
         mSwipeHelper.setDensityScale(densityScale);
         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
-        initView(getContext());
+        reinitView();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -2242,7 +2152,7 @@
                 springBack();
             } else {
                 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
-                if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+                if (mScrollAdapter.isScrolledToTop() && mScrollAnchorViewY > 0) {
                     notifyOverscrollTopListener(mScrollAnchorViewY,
                             isRubberbanded(true /* onTop */));
                 } else {
@@ -2290,7 +2200,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) {
@@ -2336,7 +2246,7 @@
         // In current design, it only use the top HUN to treat all of HUNs
         // although there are more than one HUNs
         int contentHeight = mContentHeight;
-        if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
+        if (!isExpanded() && mInHeadsUpPinnedMode) {
             contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
         }
         int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
@@ -2567,8 +2477,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;
@@ -2687,9 +2597,9 @@
                     false /* shiftPulsingWithFirst */);
             minTopPosition = firstVisibleSection.getBounds().top;
         }
-        boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1
+        boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
                 && (mAmbientState.isDozing()
-                        || (mKeyguardBypassController.getBypassEnabled() && onKeyguard));
+                        || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard));
         for (NotificationSection section : mSections) {
             int minBottomPosition = minTopPosition;
             if (section == lastSection) {
@@ -2852,7 +2762,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);
         }
     }
 
@@ -2959,7 +2870,7 @@
         } else {
             mTopPaddingOverflow = 0;
         }
-        setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled());
+        setTopPadding(topPadding, animate && !mKeyguardBypassEnabledProvider.getBypassEnabled());
         setExpandedHeight(mExpandedHeight);
     }
 
@@ -3117,7 +3028,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;
@@ -3354,9 +3265,9 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    void onViewAddedInternal(ExpandableView child) {
-        child.setOnHeightChangedListener(mOnChildHeightChangedListener);
+    private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
+        child.setOnHeightChangedListener(mOnChildHeightChangedListener);
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
@@ -3365,7 +3276,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;
@@ -3383,6 +3295,10 @@
         onViewRemovedInternal(row, childrenContainer);
     }
 
+    public void notifyGroupChildAdded(ExpandableView row) {
+        onViewAddedInternal(row);
+    }
+
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setAnimationsEnabled(boolean animationsEnabled) {
         mAnimationsEnabled = animationsEnabled;
@@ -3555,8 +3471,8 @@
             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()
-                            && mHeadsUpManager.hasPinnedHeadsUp());
+                    || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()
+                            && mInHeadsUpPinnedMode);
             if (performDisappearAnimation && !isHeadsUp) {
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3791,11 +3707,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
@@ -3804,58 +3715,16 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onTouchEvent(MotionEvent ev) {
-        NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
-        boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
-                || ev.getActionMasked() == MotionEvent.ACTION_UP;
-        handleEmptySpaceClick(ev);
-        boolean expandWantsIt = false;
-        boolean swipingInProgress = mSwipingInProgress;
-        if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) {
-            if (isCancelOrUp) {
-                mExpandHelper.onlyObserveMovements(false);
-            }
-            boolean wasExpandingBefore = mExpandingNotification;
-            expandWantsIt = mExpandHelper.onTouchEvent(ev);
-            if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
-                    && !mDisallowScrollingInThisMotion) {
-                dispatchDownEventToScroller(ev);
-            }
-        }
-        boolean scrollerWantsIt = false;
-        if (mIsExpanded && !swipingInProgress && !mExpandingNotification
-                && !mDisallowScrollingInThisMotion) {
-            scrollerWantsIt = onScrollTouch(ev);
-        }
-        boolean horizontalSwipeWantsIt = false;
-        if (!mIsBeingDragged
-                && !mExpandingNotification
-                && !mExpandedInThisMotion
-                && !mOnlyScrollingInThisMotion
-                && !mDisallowDismissInThisMotion) {
-            horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+        if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) {
+            return true;
         }
 
-        // Check if we need to clear any snooze leavebehinds
-        if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
-                && guts.getGutsContent() instanceof NotificationSnooze) {
-            NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
-            if ((ns.isExpanded() && isCancelOrUp)
-                    || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
-                // If the leavebehind is expanded we clear it on the next up event, otherwise we
-                // clear it on the next non-horizontal swipe or expand event.
-                checkSnoozeLeavebehind();
-            }
-        }
-        if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
-            mCheckForLeavebehind = true;
-        }
-        return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
+        return super.onTouchEvent(ev);
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void dispatchDownEventToScroller(MotionEvent ev) {
+    void dispatchDownEventToScroller(MotionEvent ev) {
         MotionEvent downEvent = MotionEvent.obtain(ev);
         downEvent.setAction(MotionEvent.ACTION_DOWN);
         onScrollTouch(downEvent);
@@ -3904,7 +3773,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    private boolean onScrollTouch(MotionEvent ev) {
+    boolean onScrollTouch(MotionEvent ev) {
         if (!isScrollingEnabled()) {
             return false;
         }
@@ -3993,7 +3862,7 @@
                         customOverScrollBy((int) scrollAmount, mOwnScrollY,
                                 range, getHeight() / 2);
                         // If we're scrolling, leavebehinds should be dismissed
-                        checkSnoozeLeavebehind();
+                        mController.checkSnoozeLeavebehind();
                     }
                 }
                 break;
@@ -4111,44 +3980,14 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        initDownStates(ev);
-        handleEmptySpaceClick(ev);
-
-        NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
-        boolean expandWantsIt = false;
-        boolean swipingInProgress = mSwipingInProgress;
-        if (!swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) {
-            expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
+        if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) {
+            return true;
         }
-        boolean scrollWantsIt = false;
-        if (!swipingInProgress && !mExpandingNotification) {
-            scrollWantsIt = onInterceptTouchEventScroll(ev);
-        }
-        boolean swipeWantsIt = false;
-        if (!mIsBeingDragged
-                && !mExpandingNotification
-                && !mExpandedInThisMotion
-                && !mOnlyScrollingInThisMotion
-                && !mDisallowDismissInThisMotion) {
-            swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
-        }
-        // Check if we need to clear any snooze leavebehinds
-        boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
-        if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
-                !expandWantsIt && !scrollWantsIt) {
-            mCheckForLeavebehind = false;
-            mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
-                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
-                    false /* resetMenu */);
-        }
-        if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
-            mCheckForLeavebehind = true;
-        }
-        return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
+        return super.onInterceptTouchEvent(ev);
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void handleEmptySpaceClick(MotionEvent ev) {
+    void handleEmptySpaceClick(MotionEvent ev) {
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_MOVE:
                 final float touchSlop = getTouchSlop(ev);
@@ -4167,7 +4006,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void initDownStates(MotionEvent ev) {
+    void initDownStates(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mExpandedInThisMotion = false;
             mOnlyScrollingInThisMotion = !mScroller.isFinished();
@@ -4189,7 +4028,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    private boolean onInterceptTouchEventScroll(MotionEvent ev) {
+    boolean onInterceptTouchEventScroll(MotionEvent ev) {
         if (!isScrollingEnabled()) {
             return false;
         }
@@ -4249,7 +4088,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) {
@@ -4385,30 +4224,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
-    public void closeControlsIfOutsideTouch(MotionEvent ev) {
-        NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
-        NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
-        View translatingParentView = mSwipeHelper.getTranslatingParentView();
-        View view = null;
-        if (guts != null && !guts.getGutsContent().isLeavebehind()) {
-            // Only close visible guts if they're not a leavebehind.
-            view = guts;
-        } else if (menuRow != null && menuRow.isMenuVisible()
-                && translatingParentView != null) {
-            // Checking menu
-            view = translatingParentView;
-        }
-        if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
-            // Touch was outside visible guts / menu notification, close what's visible
-            mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
-                    false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
-                    false /* resetMenu */);
-            resetExposedMenuView(true /* animate */, true /* force */);
-        }
-    }
-
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void setSwipingInProgress(boolean swiping) {
+    void setSwipingInProgress(boolean swiping) {
         mSwipingInProgress = swiping;
         if (swiping) {
             requestDisallowInterceptTouchEvent(true);
@@ -4433,32 +4249,8 @@
         }
     }
 
-    @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)
@@ -4466,39 +4258,22 @@
         return Math.max(mMaxLayoutHeight - mContentHeight, 0);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void checkSnoozeLeavebehind() {
-        if (mCheckForLeavebehind) {
-            mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
-                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
-                    false /* resetMenu */);
-            mCheckForLeavebehind = false;
-        }
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void resetCheckSnoozeLeavebehind() {
-        mCheckForLeavebehind = true;
-    }
-
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onExpansionStarted() {
         mIsExpansionChanging = true;
         mAmbientState.setExpansionChanging(true);
-        checkSnoozeLeavebehind();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onExpansionStopped() {
         mIsExpansionChanging = false;
-        resetCheckSnoozeLeavebehind();
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             resetScrollPosition();
             mStatusBar.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
-            ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
+            ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
             if (draggedViews.size() > 0) {
                 draggedViews.clear();
                 updateContinuousShadowDrawing();
@@ -4756,8 +4531,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++) {
@@ -4854,7 +4628,7 @@
      * @param lightTheme True if light theme should be used.
      */
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
-    private void updateDecorViews(boolean lightTheme) {
+    void updateDecorViews(boolean lightTheme) {
         if (lightTheme == mUsingLightTheme) {
             return;
         }
@@ -4949,7 +4723,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();
@@ -5014,11 +4788,11 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    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);
@@ -5043,6 +4817,10 @@
         handleDismissAllClipping();
     }
 
+    boolean getDismissAllInProgress() {
+        return mDismissAllInProgress;
+    }
+
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
     private void handleDismissAllClipping() {
         final int count = getChildCount();
@@ -5115,7 +4893,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void requestAnimateEverything() {
+    void requestAnimateEverything() {
         if (mIsExpanded && mAnimationsEnabled) {
             mEverythingNeedsAnimation = true;
             mNeedsAnimation = true;
@@ -5212,17 +4990,29 @@
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
+        if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
+        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     public void addContainerViewAt(View v, int index) {
         Assert.isMainThread();
         addView(v, index);
+        if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
+            mController.updateShowEmptyShadeView();
+            updateFooter();
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5273,12 +5063,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setScrimController(ScrimController scrimController) {
-        mScrimController = scrimController;
-        mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void forceNoOverlappingRendering(boolean force) {
         mForceNoOverlappingRendering = force;
     }
@@ -5329,6 +5113,10 @@
         updateScrollability();
     }
 
+    boolean isQsExpanded() {
+        return mQsExpanded;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsExpansionFraction(float qsExpansionFraction) {
         mQsExpansionFraction = qsExpansionFraction;
@@ -5453,17 +5241,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);
@@ -5556,12 +5344,15 @@
             ExpandableView child = (ExpandableView) getTransientView(i);
             child.dump(fd, pw, args);
         }
-        ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
+        ArrayList<View> draggedViews = mAmbientState.getDraggedViews();
         int draggedCount = draggedViews.size();
         pw.println("  Dragged Views: " + draggedCount);
         for (int i = 0; i < draggedCount; i++) {
-            ExpandableView child = (ExpandableView) draggedViews.get(i);
-            child.dump(fd, pw, args);
+            View view = draggedViews.get(i);
+            if (view instanceof ExpandableView) {
+                ExpandableView expandableView = (ExpandableView) view;
+                expandableView.dump(fd, pw, args);
+            }
         }
     }
 
@@ -5596,11 +5387,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setIconAreaController(NotificationIconAreaController controller) {
-        mIconAreaController = controller;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @VisibleForTesting
     void clearNotifications(
             @SelectedRows int selection,
@@ -5790,10 +5576,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.
@@ -5802,7 +5584,7 @@
      */
     public float setPulseHeight(float height) {
         mAmbientState.setPulseHeight(height);
-        if (mKeyguardBypassController.getBypassEnabled()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
             notifyAppearChangedListeners();
         }
         requestChildrenUpdate();
@@ -5823,6 +5605,10 @@
         requestChildrenUpdate();
     }
 
+    public boolean isFullyAwake() {
+        return mAmbientState.isFullyAwake();
+    }
+
     public void wakeUpFromPulse() {
         setPulseHeight(getWakeUpHeight());
         // Let's place the hidden views at the end of the pulsing notification to make sure we have
@@ -5852,17 +5638,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) {
@@ -5889,6 +5666,83 @@
         return mController;
     }
 
+    void setCurrentUserid(int userId) {
+        mCurrentUserId = userId;
+    }
+
+    void addSwipedOutView(View v) {
+        mSwipedOutViews.add(v);
+    }
+
+    void addDraggedView(View view) {
+        mAmbientState.onBeginDrag(view);
+    }
+
+    void removeDraggedView(View view) {
+        mAmbientState.onDragFinished(view);
+    }
+
+    void setTopHeadsUpEntry(NotificationEntry topEntry) {
+        mTopHeadsUpEntry = topEntry;
+    }
+
+    void setNumHeadsUp(long numHeadsUp) {
+        mNumHeadsUp = numHeadsUp;
+        mAmbientState.setHasAlertEntries(numHeadsUp > 0);
+    }
+
+    boolean getSwipingInProgress() {
+        return mSwipingInProgress;
+    }
+
+    public boolean getIsExpanded() {
+        return mIsExpanded;
+    }
+
+    boolean getOnlyScrollingInThisMotion() {
+        return mOnlyScrollingInThisMotion;
+    }
+
+    ExpandHelper getExpandHelper() {
+        return mExpandHelper;
+    }
+
+    boolean isExpandingNotification() {
+        return mExpandingNotification;
+    }
+
+    boolean getDisallowScrollingInThisMotion() {
+        return mDisallowScrollingInThisMotion;
+    }
+
+    boolean isBeingDragged() {
+        return mIsBeingDragged;
+    }
+
+    boolean getExpandedInThisMotion() {
+        return mExpandedInThisMotion;
+    }
+
+    boolean getDisallowDismissInThisMotion() {
+        return mDisallowDismissInThisMotion;
+    }
+
+    void setCheckForLeaveBehind(boolean checkForLeaveBehind) {
+        mCheckForLeavebehind = checkForLeaveBehind;
+    }
+
+    void setTouchHandler(NotificationStackScrollLayoutController.TouchHandler touchHandler) {
+        mTouchHandler = touchHandler;
+    }
+
+    boolean isSwipingInProgress() {
+        return mSwipingInProgress;
+    }
+
+    boolean getCheckSnoozeLeaveBehind() {
+        return mCheckForLeavebehind;
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -5956,7 +5810,7 @@
         mSectionsManager.updateSectionBoundaries(reason);
     }
 
-    private void updateContinuousBackgroundDrawing() {
+    void updateContinuousBackgroundDrawing() {
         boolean continuousBackground = !mAmbientState.isFullyAwake()
                 && !mAmbientState.getDraggedViews().isEmpty();
         if (continuousBackground != mContinuousBackgroundUpdate) {
@@ -5970,7 +5824,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void updateContinuousShadowDrawing() {
+    void updateContinuousShadowDrawing() {
         boolean continuousShadowUpdate = mAnimationRunning
                 || !mAmbientState.getDraggedViews().isEmpty();
         if (continuousShadowUpdate != mContinuousShadowUpdate) {
@@ -5984,7 +5838,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void resetExposedMenuView(boolean animate, boolean force) {
+    private void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
 
@@ -6244,277 +6098,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 =
-            new NotificationSwipeHelper.NotificationCallback() {
-        @Override
-        public void onDismiss() {
-            mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
-                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
-                    false /* resetMenu */);
-        }
-
-        @Override
-        public void onSnooze(StatusBarNotification sbn,
-                NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
-            mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
-        }
-
-        @Override
-        public void onSnooze(StatusBarNotification sbn, int hours) {
-            mStatusBar.setNotificationSnoozed(sbn, hours);
-        }
-
-        @Override
-        public boolean shouldDismissQuickly() {
-            return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
-        }
-
-        @Override
-        public void onDragCancelled(View v) {
-            setSwipingInProgress(false);
-            mFalsingManager.onNotificationStopDismissing();
-        }
-
-        /**
-         * Handles cleanup after the given {@code view} has been fully swiped out (including
-         * re-invoking dismiss logic in case the notification has not made its way out yet).
-         */
-        @Override
-        public void onChildDismissed(View view) {
-            if (!(view instanceof ActivatableNotificationView)) {
-                return;
-            }
-            ActivatableNotificationView row = (ActivatableNotificationView) view;
-            if (!row.isDismissed()) {
-                handleChildViewDismissed(view);
-            }
-            ViewGroup transientContainer = row.getTransientContainer();
-            if (transientContainer != null) {
-                transientContainer.removeTransientView(view);
-            }
-        }
-
-        /**
-         * Starts up notification dismiss and tells the notification, if any, to remove itself from
-         * layout.
-         *
-         * @param view view (e.g. notification) to dismiss from the layout
-         */
-
-        public void handleChildViewDismissed(View view) {
-            setSwipingInProgress(false);
-            if (mDismissAllInProgress) {
-                return;
-            }
-
-            boolean isBlockingHelperShown = false;
-
-            mAmbientState.onDragFinished(view);
-            updateContinuousShadowDrawing();
-
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-                if (row.isHeadsUp()) {
-                    mHeadsUpManager.addSwipedOutNotification(
-                            row.getEntry().getSbn().getKey());
-                }
-                isBlockingHelperShown =
-                        row.performDismissWithBlockingHelper(false /* fromAccessibility */);
-            }
-
-            if (view instanceof PeopleHubView) {
-                mSectionsManager.hidePeopleRow();
-            }
-
-            if (!isBlockingHelperShown) {
-                mSwipedOutViews.add(view);
-            }
-            mFalsingManager.onNotificationDismissed();
-            if (mFalsingManager.shouldEnforceBouncer()) {
-                mStatusBar.executeRunnableDismissingKeyguard(
-                        null,
-                        null /* cancelAction */,
-                        false /* dismissShade */,
-                        true /* afterKeyguardGone */,
-                        false /* deferred */);
-            }
-        }
-
-        @Override
-        public boolean isAntiFalsingNeeded() {
-            return onKeyguard();
-        }
-
-        @Override
-        public View getChildAtPosition(MotionEvent ev) {
-            View child = NotificationStackScrollLayout.this.getChildAtPosition(
-                    ev.getX(),
-                    ev.getY(),
-                    true /* requireMinHeight */,
-                    false /* ignoreDecors */);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                ExpandableNotificationRow parent = row.getNotificationParent();
-                if (parent != null && parent.areChildrenExpanded()
-                        && (parent.areGutsExposed()
-                        || mSwipeHelper.getExposedMenuView() == parent
-                        || (parent.getAttachedChildren().size() == 1
-                        && parent.getEntry().isClearable()))) {
-                    // In this case the group is expanded and showing the menu for the
-                    // group, further interaction should apply to the group, not any
-                    // child notifications so we use the parent of the child. We also do the same
-                    // if we only have a single child.
-                    child = parent;
-                }
-            }
-            return child;
-        }
-
-        @Override
-        public void onBeginDrag(View v) {
-            mFalsingManager.onNotificationStartDismissing();
-            setSwipingInProgress(true);
-            mAmbientState.onBeginDrag((ExpandableView) v);
-            updateContinuousShadowDrawing();
-            updateContinuousBackgroundDrawing();
-            requestChildrenUpdate();
-        }
-
-        @Override
-        public void onChildSnappedBack(View animView, float targetLeft) {
-            mAmbientState.onDragFinished(animView);
-            updateContinuousShadowDrawing();
-            updateContinuousBackgroundDrawing();
-            if (animView instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
-                if (row.isPinned() && !canChildBeDismissed(row)
-                        && row.getEntry().getSbn().getNotification().fullScreenIntent
-                                == null) {
-                    mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(),
-                            true /* removeImmediately */);
-                }
-            }
-        }
-
-        @Override
-        public boolean updateSwipeProgress(View animView, boolean dismissable,
-                float swipeProgress) {
-            // Returning true prevents alpha fading.
-            return !mFadeNotificationsOnDismiss;
-        }
-
-        @Override
-        public float getFalsingThresholdFactor() {
-            return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        }
-
-        @Override
-        public int getConstrainSwipeStartPosition() {
-            NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
-            if (menuRow != null) {
-                return Math.abs(menuRow.getMenuSnapTarget());
-            }
-            return 0;
-        }
-
-        @Override
-        public boolean canChildBeDismissed(View v) {
-            return NotificationStackScrollLayout.canChildBeDismissed(v);
-        }
-
-        @Override
-        public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
-            //TODO: b/131242807 for why this doesn't do anything with direction
-            return canChildBeDismissed(v);
-        }
-    };
-
-    private static boolean canChildBeDismissed(View v) {
+    static boolean canChildBeDismissed(View v) {
         if (v instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
             if (row.isBlockingHelperShowingAndTranslationFinished()) {
@@ -6557,7 +6141,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<>();
@@ -6586,7 +6170,7 @@
             }
             if (selectedRows == ROWS_ALL) {
                 try {
-                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+                    mBarService.onClearAllNotifications(mCurrentUserId);
                 } catch (Exception ex) {
                 }
             }
@@ -6608,6 +6192,14 @@
                     NotificationLogger.getNotificationLocation(entry)));
     }
 
+    public void setKeyguardMediaControllorVisible(boolean keyguardMediaControllorVisible) {
+        mKeyguardMediaControllorVisible = keyguardMediaControllorVisible;
+    }
+
+    void resetCheckSnoozeLeavebehind() {
+        setCheckForLeaveBehind(true);
+    }
+
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6616,8 +6208,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,
@@ -6661,7 +6252,7 @@
         @Override
         public void onTouchSlopExceeded() {
             cancelLongPress();
-            checkSnoozeLeavebehind();
+            mController.checkSnoozeLeavebehind();
         }
 
         @Override
@@ -6695,7 +6286,7 @@
         @Override
         public boolean isDragDownAnywhereEnabled() {
             return mStatusbarStateController.getState() == StatusBarState.KEYGUARD
-                    && !mKeyguardBypassController.getBypassEnabled();
+                    && !mKeyguardBypassEnabledProvider.getBypassEnabled();
         }
     };
 
@@ -6878,4 +6469,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
index 53d3b75..f8ee0a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -17,36 +17,66 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
+import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
 import android.view.Display;
+import android.view.MotionEvent;
 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.ExpandHelper;
+import com.android.systemui.Gefingerpoken;
+import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
+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.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.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 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.NotificationIconAreaController;
 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.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 
 import java.util.function.BiConsumer;
@@ -54,19 +84,418 @@
 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 FalsingManager mFalsingManager;
+    private final NotificationSectionsManager mNotificationSectionsManager;
+    private final Resources mResources;
+    private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
+    private final ScrimController mScrimController;
+    private final KeyguardMediaController mKeyguardMediaController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final SysuiColorExtractor mColorExtractor;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    // TODO: StatusBar should be encapsulated behind a Controller
+    private final StatusBar mStatusBar;
+
+    private NotificationStackScrollLayout mView;
+    private boolean mFadeNotificationsOnDismiss;
+    private NotificationSwipeHelper mSwipeHelper;
+    private boolean mShowEmptyShadeView;
+    private int mBarState;
+
     private final NotificationListContainerImpl mNotificationListContainer =
             new NotificationListContainerImpl();
-    private NotificationStackScrollLayout mView;
+
+    @VisibleForTesting
+    final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    mConfigurationController.addCallback(mConfigurationListener);
+                    mZenModeController.addCallback(mZenModeControllerCallback);
+                    mBarState = mStatusBarStateController.getState();
+                    mStatusBarStateController.addCallback(
+                            mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mConfigurationController.removeCallback(mConfigurationListener);
+                    mZenModeController.removeCallback(mZenModeControllerCallback);
+                    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() {
+            updateShowEmptyShadeView();
+            mView.reinflateViews();
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            updateShowEmptyShadeView();
+            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 == KEYGUARD) {
+                        mView.requestAnimateEverything();
+                    }
+                }
+
+                @Override
+                public void onStateChanged(int newState) {
+                    mBarState = newState;
+                    mView.setStatusBarState(mBarState);
+                }
+
+                @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) {
+            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 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
+                    mSwipeHelper.resetExposedMenuView(false, true);
+                }
+            }
+        }
+    };
+
+    private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
+            new NotificationSwipeHelper.NotificationCallback() {
+
+                @Override
+                public void onDismiss() {
+                    mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
+                            false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                            false /* resetMenu */);
+                }
+
+                @Override
+                public void onSnooze(StatusBarNotification sbn,
+                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                    mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
+                }
+
+                @Override
+                public void onSnooze(StatusBarNotification sbn, int hours) {
+                    mStatusBar.setNotificationSnoozed(sbn, hours);
+                }
+
+                @Override
+                public boolean shouldDismissQuickly() {
+                    return mView.isExpanded() && mView.isFullyAwake();
+                }
+
+                @Override
+                public void onDragCancelled(View v) {
+                    mView.setSwipingInProgress(false);
+                    mFalsingManager.onNotificationStopDismissing();
+                }
+
+                /**
+                 * Handles cleanup after the given {@code view} has been fully swiped out (including
+                 * re-invoking dismiss logic in case the notification has not made its way out yet).
+                 */
+                @Override
+                public void onChildDismissed(View view) {
+                    if (!(view instanceof ActivatableNotificationView)) {
+                        return;
+                    }
+                    ActivatableNotificationView row = (ActivatableNotificationView) view;
+                    if (!row.isDismissed()) {
+                        handleChildViewDismissed(view);
+                    }
+                    ViewGroup transientContainer = row.getTransientContainer();
+                    if (transientContainer != null) {
+                        transientContainer.removeTransientView(view);
+                    }
+                }
+
+                /**
+                 * Starts up notification dismiss and tells the notification, if any, to remove
+                 * itself from the layout.
+                 *
+                 * @param view view (e.g. notification) to dismiss from the layout
+                 */
+
+                public void handleChildViewDismissed(View view) {
+                    mView.setSwipingInProgress(false);
+                    if (mView.getDismissAllInProgress()) {
+                        return;
+                    }
+
+                    boolean isBlockingHelperShown = false;
+
+                    mView.removeDraggedView(view);
+                    mView.updateContinuousShadowDrawing();
+
+                    if (view instanceof ExpandableNotificationRow) {
+                        ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                        if (row.isHeadsUp()) {
+                            mHeadsUpManager.addSwipedOutNotification(
+                                    row.getEntry().getSbn().getKey());
+                        }
+                        isBlockingHelperShown =
+                                row.performDismissWithBlockingHelper(false /* fromAccessibility */);
+                    }
+
+                    if (view instanceof PeopleHubView) {
+                        mNotificationSectionsManager.hidePeopleRow();
+                    }
+
+                    if (!isBlockingHelperShown) {
+                        mView.addSwipedOutView(view);
+                    }
+                    mFalsingManager.onNotificationDismissed();
+                    if (mFalsingManager.shouldEnforceBouncer()) {
+                        mStatusBar.executeRunnableDismissingKeyguard(
+                                null,
+                                null /* cancelAction */,
+                                false /* dismissShade */,
+                                true /* afterKeyguardGone */,
+                                false /* deferred */);
+                    }
+                }
+
+                @Override
+                public boolean isAntiFalsingNeeded() {
+                    return mView.onKeyguard();
+                }
+
+                @Override
+                public View getChildAtPosition(MotionEvent ev) {
+                    View child = mView.getChildAtPosition(
+                            ev.getX(),
+                            ev.getY(),
+                            true /* requireMinHeight */,
+                            false /* ignoreDecors */);
+                    if (child instanceof ExpandableNotificationRow) {
+                        ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                        ExpandableNotificationRow parent = row.getNotificationParent();
+                        if (parent != null && parent.areChildrenExpanded()
+                                && (parent.areGutsExposed()
+                                || mSwipeHelper.getExposedMenuView() == parent
+                                || (parent.getAttachedChildren().size() == 1
+                                && parent.getEntry().isClearable()))) {
+                            // In this case the group is expanded and showing the menu for the
+                            // group, further interaction should apply to the group, not any
+                            // child notifications so we use the parent of the child. We also do the
+                            // same if we only have a single child.
+                            child = parent;
+                        }
+                    }
+                    return child;
+                }
+
+                @Override
+                public void onBeginDrag(View v) {
+                    mFalsingManager.onNotificationStartDismissing();
+                    mView.setSwipingInProgress(true);
+                    mView.addDraggedView(v);
+                    mView.updateContinuousShadowDrawing();
+                    mView.updateContinuousBackgroundDrawing();
+                    mView.requestChildrenUpdate();
+                }
+
+                @Override
+                public void onChildSnappedBack(View animView, float targetLeft) {
+                    mView.addDraggedView(animView);
+                    mView.updateContinuousShadowDrawing();
+                    mView.updateContinuousBackgroundDrawing();
+                    if (animView instanceof ExpandableNotificationRow) {
+                        ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
+                        if (row.isPinned() && !canChildBeDismissed(row)
+                                && row.getEntry().getSbn().getNotification().fullScreenIntent
+                                == null) {
+                            mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(),
+                                    true /* removeImmediately */);
+                        }
+                    }
+                }
+
+                @Override
+                public boolean updateSwipeProgress(View animView, boolean dismissable,
+                        float swipeProgress) {
+                    // Returning true prevents alpha fading.
+                    return !mFadeNotificationsOnDismiss;
+                }
+
+                @Override
+                public float getFalsingThresholdFactor() {
+                    return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+                }
+
+                @Override
+                public int getConstrainSwipeStartPosition() {
+                    NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+                    if (menuRow != null) {
+                        return Math.abs(menuRow.getMenuSnapTarget());
+                    }
+                    return 0;
+                }
+
+                @Override
+                public boolean canChildBeDismissed(View v) {
+                    return NotificationStackScrollLayout.canChildBeDismissed(v);
+                }
+
+                @Override
+                public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
+                    //TODO: b/131242807 for why this doesn't do anything with direction
+                    return canChildBeDismissed(v);
+                }
+            };
+
+    private final OnHeadsUpChangedListener mOnHeadsUpChangedListener =
+            new OnHeadsUpChangedListener() {
+        @Override
+        public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+            mView.setInHeadsUpPinnedMode(inPinnedMode);
+        }
+
+        @Override
+        public void onHeadsUpPinned(NotificationEntry entry) {
+
+        }
+
+        @Override
+        public void onHeadsUpUnPinned(NotificationEntry entry) {
+
+        }
+
+        @Override
+        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+            long numEntries = mHeadsUpManager.getAllEntries().count();
+            NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
+            mView.setNumHeadsUp(numEntries);
+            mView.setTopHeadsUpEntry(topEntry);
+        }
+    };
+
+    private final ZenModeController.Callback mZenModeControllerCallback =
+            new ZenModeController.Callback() {
+                @Override
+                public void onZenChanged(int zen) {
+                    updateShowEmptyShadeView();
+                }
+            };
 
     @Inject
     public NotificationStackScrollLayoutController(
@@ -74,32 +503,106 @@
             NotificationGutsManager notificationGutsManager,
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
-            TunerService tunerService) {
+            TunerService tunerService,
+            DynamicPrivacyController dynamicPrivacyController,
+            ConfigurationController configurationController,
+            SysuiStatusBarStateController statusBarStateController,
+            KeyguardMediaController keyguardMediaController,
+            KeyguardBypassController keyguardBypassController,
+            ZenModeController zenModeController,
+            SysuiColorExtractor colorExtractor,
+            NotificationLockscreenUserManager lockscreenUserManager,
+            MetricsLogger metricsLogger,
+            FalsingManager falsingManager,
+            NotificationSectionsManager notificationSectionsManager,
+            @Main Resources resources,
+            NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
+            StatusBar statusBar,
+            ScrimController scrimController) {
         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;
+        mFalsingManager = falsingManager;
+        mNotificationSectionsManager = notificationSectionsManager;
+        mResources = resources;
+        mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
+        mStatusBar = statusBar;
+        mScrimController = scrimController;
     }
 
     public void attach(NotificationStackScrollLayout view) {
         mView = view;
         mView.setController(this);
+        mView.setTouchHandler(new TouchHandler());
 
-        if (mAllowLongPress) {
-            mView.setLongPressListener(mNotificationGutsManager::openGuts);
-        }
+        mSwipeHelper = mNotificationSwipeHelperBuilder
+                .setSwipeDirection(SwipeHelper.X)
+                .setNotificationCallback(mNotificationCallback)
+                .setOnMenuEventListener(mMenuEventListener)
+                .build();
+
+        mView.initView(mView.getContext(), mKeyguardBypassController::getBypassEnabled,
+                mSwipeHelper);
 
         mHeadsUpManager.addListener(mNotificationRoundnessManager); // TODO: why is this here?
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+        mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
+        mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
+
+        mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
+
+        mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
+        mView.setCurrentUserid(mLockscreenUserManager.getCurrentUserId());
+
+        mFadeNotificationsOnDismiss =  // TODO: this should probably be injected directly
+                mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
 
         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));
+        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());
             }
-        }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
+            mView.requestChildrenUpdate();
+            return Unit.INSTANCE;
+        });
+
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
     }
 
     public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
@@ -299,11 +802,17 @@
     }
 
     public void checkSnoozeLeavebehind() {
-        mView.checkSnoozeLeavebehind();
+        if (mView.getCheckSnoozeLeaveBehind()) {
+            mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
+            mView.setCheckForLeaveBehind(false);
+        }
     }
 
     public void setQsExpanded(boolean expanded) {
         mView.setQsExpanded(expanded);
+        updateShowEmptyShadeView();
     }
 
     public void setScrollingEnabled(boolean enabled) {
@@ -322,10 +831,6 @@
         mView.updateTopPadding(qsHeight, animate);
     }
 
-    public void resetCheckSnoozeLeavebehind() {
-        mView.resetCheckSnoozeLeavebehind();
-    }
-
     public boolean isScrolledToBottom() {
         return mView.isScrolledToBottom();
     }
@@ -376,9 +881,11 @@
 
     public void onExpansionStarted() {
         mView.onExpansionStarted();
+        checkSnoozeLeavebehind();
     }
 
     public void onExpansionStopped() {
+        mView.setCheckForLeaveBehind(false);
         mView.onExpansionStopped();
     }
 
@@ -414,8 +921,21 @@
         return mView.getFooterViewHeightWithPadding();
     }
 
-    public void updateEmptyShadeView(boolean visible) {
-        mView.updateEmptyShadeView(visible);
+    /**
+     * Update whether we should show the empty shade view (no notifications in the shade).
+     * If so, send the update to our view.
+     */
+    public void updateShowEmptyShadeView() {
+        mShowEmptyShadeView = mBarState != KEYGUARD
+                && !mView.isQsExpanded()
+                && mView.getVisibleNotificationCount() == 0;
+        mView.updateEmptyShadeView(
+                mShowEmptyShadeView,
+                mZenModeController.areNotificationsHiddenInShade());
+    }
+
+    public boolean isShowingEmptyShadeView() {
+        return mShowEmptyShadeView;
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
@@ -463,8 +983,31 @@
         return mView.hasActiveClearableNotifications(selection);
     }
 
+    /**
+     * Set the maximum number of notifications that can currently be displayed
+     */
+    public void setMaxDisplayedNotifications(int maxNotifications) {
+        mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
+    }
+
     public RemoteInputController.Delegate createDelegate() {
-        return mView.createDelegate();
+        return new RemoteInputController.Delegate() {
+            public void setRemoteInputActive(NotificationEntry entry,
+                    boolean remoteInputActive) {
+                mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+                entry.notifyHeightChanged(true /* needsAnimation */);
+                updateFooter();
+            }
+
+            public void lockScrollTo(NotificationEntry entry) {
+                mView.lockScrollTo(entry.getRow());
+            }
+
+            public void requestDisallowLongPressAndDismiss() {
+                mView.requestDisallowLongPress();
+                mView.requestDisallowDismiss();
+            }
+        };
     }
 
     public void updateSectionBoundaries(String reason) {
@@ -479,10 +1022,6 @@
         mView.updateFooter();
     }
 
-    public void updateIconAreaViews() {
-        mView.updateIconAreaViews();
-    }
-
     public void onUpdateRowStates() {
         mView.onUpdateRowStates();
     }
@@ -504,10 +1043,6 @@
         mView.setNotificationPanelController(notificationPanelViewController);
     }
 
-    public void setIconAreaController(NotificationIconAreaController controller) {
-        mView.setIconAreaController(controller);
-    }
-
     public void setStatusBar(StatusBar statusBar) {
         mView.setStatusBar(statusBar);
     }
@@ -520,18 +1055,10 @@
         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);
     }
@@ -564,7 +1091,7 @@
         return mView.calculateGapHeight(previousView, child, count);
     }
 
-    public NotificationRoundnessManager getNoticationRoundessManager() {
+    NotificationRoundnessManager getNoticationRoundessManager() {
         return mNotificationRoundnessManager;
     }
 
@@ -572,6 +1099,32 @@
         return mNotificationListContainer;
     }
 
+    public void resetCheckSnoozeLeavebehind() {
+        mView.resetCheckSnoozeLeavebehind();
+    }
+
+    public void closeControlsIfOutsideTouch(MotionEvent ev) {
+        NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
+        NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+        View translatingParentView = mSwipeHelper.getTranslatingParentView();
+        View view = null;
+        if (guts != null && !guts.getGutsContent().isLeavebehind()) {
+            // Only close visible guts if they're not a leavebehind.
+            view = guts;
+        } else if (menuRow != null && menuRow.isMenuVisible()
+                && translatingParentView != null) {
+            // Checking menu
+            view = translatingParentView;
+        }
+        if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
+            // Touch was outside visible guts / menu notification, close what's visible
+            mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
+                    false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
+            mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */);
+        }
+    }
+
     private class NotificationListContainerImpl implements NotificationListContainer {
         @Override
         public void setChildTransferInProgress(boolean childTransferInProgress) {
@@ -585,7 +1138,7 @@
 
         @Override
         public void notifyGroupChildAdded(ExpandableView row) {
-            mView.onViewAddedInternal(row);
+            mView.notifyGroupChildAdded(row);
         }
 
         @Override
@@ -646,12 +1199,12 @@
 
         @Override
         public void resetExposedMenuView(boolean animate, boolean force) {
-            mView.resetExposedMenuView(animate, force);
+            mSwipeHelper.resetExposedMenuView(animate, force);
         }
 
         @Override
         public NotificationSwipeActionHelper getSwipeActionHelper() {
-            return mView.getSwipeActionHelper();
+            return mSwipeHelper;
         }
 
         @Override
@@ -710,4 +1263,99 @@
             mView.setWillExpand(willExpand);
         }
     }
+
+    class TouchHandler implements Gefingerpoken {
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            mView.initDownStates(ev);
+            mView.handleEmptySpaceClick(ev);
+
+            NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
+            boolean expandWantsIt = false;
+            boolean swipingInProgress = mView.isSwipingInProgress();
+            if (!swipingInProgress && !mView.getOnlyScrollingInThisMotion() && guts == null) {
+                expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
+            }
+            boolean scrollWantsIt = false;
+            if (!swipingInProgress && !mView.isExpandingNotification()) {
+                scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
+            }
+            boolean swipeWantsIt = false;
+            if (!mView.isBeingDragged()
+                    && !mView.isExpandingNotification()
+                    && !mView.getExpandedInThisMotion()
+                    && !mView.getOnlyScrollingInThisMotion()
+                    && !mView.getDisallowDismissInThisMotion()) {
+                swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
+            }
+            // Check if we need to clear any snooze leavebehinds
+            boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
+            if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
+                    !expandWantsIt && !scrollWantsIt) {
+                mView.setCheckForLeaveBehind(false);
+                mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
+                        false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
+            }
+            if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+                mView.setCheckForLeaveBehind(true);
+            }
+            return swipeWantsIt || scrollWantsIt || expandWantsIt;
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
+            boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+                    || ev.getActionMasked() == MotionEvent.ACTION_UP;
+            mView.handleEmptySpaceClick(ev);
+            boolean expandWantsIt = false;
+            boolean swipingInProgress = mView.getSwipingInProgress();
+            boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
+            boolean expandingNotification = mView.isExpandingNotification();
+            if (mView.getIsExpanded() && !swipingInProgress && !onlyScrollingInThisMotion
+                    && guts == null) {
+                ExpandHelper expandHelper = mView.getExpandHelper();
+                if (isCancelOrUp) {
+                    expandHelper.onlyObserveMovements(false);
+                }
+                boolean wasExpandingBefore = expandingNotification;
+                expandWantsIt = expandHelper.onTouchEvent(ev);
+                expandingNotification = mView.isExpandingNotification();
+                if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore
+                        && !mView.getDisallowScrollingInThisMotion()) {
+                    mView.dispatchDownEventToScroller(ev);
+                }
+            }
+            boolean scrollerWantsIt = false;
+            if (mView.isExpanded() && !swipingInProgress && !expandingNotification
+                    && !mView.getDisallowScrollingInThisMotion()) {
+                scrollerWantsIt = mView.onScrollTouch(ev);
+            }
+            boolean horizontalSwipeWantsIt = false;
+            if (!mView.isBeingDragged()
+                    && !expandingNotification
+                    && !mView.getExpandedInThisMotion()
+                    && !onlyScrollingInThisMotion
+                    && !mView.getDisallowDismissInThisMotion()) {
+                horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+            }
+
+            // Check if we need to clear any snooze leavebehinds
+            if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
+                    && guts.getGutsContent() instanceof NotificationSnooze) {
+                NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
+                if ((ns.isExpanded() && isCancelOrUp)
+                        || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
+                    // If the leavebehind is expanded we clear it on the next up event, otherwise we
+                    // clear it on the next non-horizontal swipe or expand event.
+                    checkSnoozeLeavebehind();
+                }
+            }
+            if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+                mView.setCheckForLeaveBehind(true);
+            }
+            return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index d3b8a8c..ba01c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -19,15 +19,17 @@
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
-import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.service.notification.StatusBarNotification;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -36,6 +38,8 @@
 
 import java.lang.ref.WeakReference;
 
+import javax.inject.Inject;
+
 class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper {
 
     @VisibleForTesting
@@ -58,10 +62,10 @@
     private boolean mPulsing;
 
     NotificationSwipeHelper(
-            int swipeDirection, NotificationCallback callback, Context context,
-            NotificationMenuRowPlugin.OnMenuEventListener menuListener,
-            FalsingManager falsingManager) {
-        super(swipeDirection, callback, context, falsingManager);
+            Resources resources, ViewConfiguration viewConfiguration,
+            FalsingManager falsingManager, int swipeDirection, NotificationCallback callback,
+            NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+        super(swipeDirection, callback, resources, viewConfiguration, falsingManager);
         mMenuListener = menuListener;
         mCallback = callback;
         mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -74,7 +78,7 @@
     public void clearTranslatingParentView() { setTranslatingParentView(null); }
 
     @VisibleForTesting
-    protected void setTranslatingParentView(View view) { mTranslatingParentView = view; };
+    protected void setTranslatingParentView(View view) { mTranslatingParentView = view; }
 
     public void setExposedMenuView(View view) {
         mMenuExposedView = view;
@@ -90,7 +94,7 @@
 
     @VisibleForTesting
     void setCurrentMenuRow(NotificationMenuRowPlugin menuRow) {
-        mCurrMenuRowRef = menuRow != null ? new WeakReference(menuRow) : null;
+        mCurrMenuRowRef = menuRow != null ? new WeakReference<>(menuRow) : null;
     }
 
     public NotificationMenuRowPlugin getCurrentMenuRow() {
@@ -223,7 +227,7 @@
                 || (isFastNonDismissGesture && isAbleToShowMenu);
         int menuSnapTarget = menuRow.getMenuSnapTarget();
         boolean isNonFalseMenuRevealingGesture =
-                !isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu;
+                !isFalseGesture() && isMenuRevealingGestureAwayFromMenu;
         if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
                 && menuSnapTarget != 0) {
             // Menu has not been snapped to previously and this is menu revealing gesture
@@ -470,4 +474,42 @@
 
         void onDismiss();
     }
+
+    static class Builder {
+        private final Resources mResources;
+        private final ViewConfiguration mViewConfiguration;
+        private final FalsingManager mFalsingManager;
+        private int mSwipeDirection;
+        private NotificationCallback mNotificationCallback;
+        private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+
+        @Inject
+        Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
+                FalsingManager falsingManager) {
+            mResources = resources;
+            mViewConfiguration = viewConfiguration;
+            mFalsingManager = falsingManager;
+        }
+
+        Builder setSwipeDirection(int swipeDirection) {
+            mSwipeDirection = swipeDirection;
+            return this;
+        }
+
+        Builder setNotificationCallback(NotificationCallback notificationCallback) {
+            mNotificationCallback = notificationCallback;
+            return this;
+        }
+
+        Builder setOnMenuEventListener(
+                NotificationMenuRowPlugin.OnMenuEventListener onMenuEventListener) {
+            mOnMenuEventListener = onMenuEventListener;
+            return this;
+        }
+
+        NotificationSwipeHelper build() {
+            return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
+                    mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+        }
+    }
 }
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 9d920c3..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;
@@ -58,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";
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/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 de74d4e..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,6 +31,8 @@
 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;
@@ -41,7 +43,6 @@
 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;
@@ -49,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<>();
@@ -82,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;
@@ -106,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;
@@ -125,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.
@@ -137,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;
@@ -255,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 */);
@@ -297,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/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/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 858023d..ba94202 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -317,7 +318,9 @@
         // We snap back if the current translation is not far enough
         boolean snapBack = false;
         if (mCallback.needsAntiFalsing()) {
-            snapBack = snapBack || mFalsingManager.isFalseTouch();
+            snapBack = snapBack || mFalsingManager.isFalseTouch(
+                    mTargetedView == mRightIcon
+                            ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
         }
         snapBack = snapBack || isBelowFalsingThreshold();
 
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 982773a..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,7 @@
 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;
@@ -41,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;
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 8e9dcb4..94d1bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -27,17 +27,17 @@
 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<>();
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 0e413fb..bda35fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,7 +8,6 @@
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
@@ -21,6 +20,7 @@
 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;
@@ -34,19 +34,23 @@
 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,
         DemoMode {
@@ -62,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;
@@ -77,8 +82,8 @@
     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;
@@ -95,16 +100,14 @@
             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,
@@ -112,8 +115,9 @@
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
             BubbleController bubbleController,
-            DemoModeController demoModeController) {
-        mStatusBar = statusBar;
+            DemoModeController demoModeController,
+            DarkIconDispatcher darkIconDispatcher,
+            StatusBarWindowController statusBarWindowController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
@@ -124,12 +128,14 @@
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
         mBubbleController = bubbleController;
-        notificationListener.addNotificationSettingsListener(mSettingsListener);
         mDemoModeController = demoModeController;
         mDemoModeController.addCallback(this);
+        mStatusBarWindowController = statusBarWindowController;
+        notificationListener.addNotificationSettingsListener(mSettingsListener);
 
         initializeNotificationAreaViews(context);
         reloadAodColor();
+        darkIconDispatcher.addDarkReceiver(this);
     }
 
     protected View inflateIconArea(LayoutInflater inflater) {
@@ -146,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();
@@ -199,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) {
@@ -238,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,
@@ -310,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();
@@ -391,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);
                     }
@@ -607,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));
+                }
             }
         }
     }
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 42fbe59..ab3fac9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,8 @@
 
 import static android.view.View.GONE;
 
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
@@ -31,6 +33,7 @@
 import android.app.StatusBarManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -66,10 +69,13 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
@@ -85,7 +91,6 @@
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 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;
@@ -100,7 +105,9 @@
 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.collection.render.ShadeViewManager;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -114,7 +121,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
 
 import java.io.FileDescriptor;
@@ -126,7 +132,6 @@
 import java.util.function.Function;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 @StatusBarComponent.StatusBarScope
 public class NotificationPanelViewController extends PanelViewController {
@@ -163,9 +168,6 @@
             mOnHeadsUpChangedListener =
             new MyOnHeadsUpChangedListener();
     private final HeightListener mHeightListener = new HeightListener();
-    private final ZenModeControllerCallback
-            mZenModeControllerCallback =
-            new ZenModeControllerCallback();
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
     private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
     private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
@@ -173,10 +175,10 @@
     private final NotificationPanelView mView;
     private final MetricsLogger mMetricsLogger;
     private final ActivityManager mActivityManager;
-    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.
@@ -229,7 +231,7 @@
                         BiometricSourceType biometricSourceType) {
                     boolean
                             keyguardOrShadeLocked =
-                            mBarState == StatusBarState.KEYGUARD
+                            mBarState == KEYGUARD
                                     || mBarState == StatusBarState.SHADE_LOCKED;
                     if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
                             && !mDelayShowingKeyguardStatusBar
@@ -256,7 +258,12 @@
     private final ConversationNotificationManager mConversationNotificationManager;
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final Provider<KeyguardClockSwitchController> mKeyguardClockSwitchControllerProvider;
+    private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
+    // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
+    private final int mMaxKeyguardNotifications;
+    // Current max allowed keyguard notifications determined by measuring the panel
+    private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -331,8 +338,6 @@
     private boolean mKeyguardStatusViewAnimating;
     private ValueAnimator mQsSizeChangeAnimator;
 
-    private boolean mShowEmptyShadeView;
-
     private boolean mQsScrimEnabled = true;
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
@@ -480,6 +485,7 @@
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
+            @Main Resources resources,
             InjectionInflationController injectionInflationController,
             NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
@@ -493,7 +499,7 @@
             LatencyTracker latencyTracker, PowerManager powerManager,
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
-            ActivityManager activityManager, ZenModeController zenModeController,
+            ActivityManager activityManager,
             ConfigurationController configurationController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -501,21 +507,22 @@
             MediaHierarchyManager mediaHierarchyManager,
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider,
-            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            NotificationIconAreaController notificationIconAreaController,
+            KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
         mView = view;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
-        mZenModeController = zenModeController;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        mKeyguardClockSwitchControllerProvider = keyguardClockSwitchControllerProvider;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mNotificationIconAreaController = notificationIconAreaController;
+        mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -580,6 +587,7 @@
             mView.getOverlay().add(new DebugDrawable());
         }
 
+        mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         onFinishInflate();
     }
 
@@ -589,8 +597,10 @@
         mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
 
         KeyguardClockSwitchController keyguardClockSwitchController =
-                mKeyguardClockSwitchControllerProvider.get();
-        keyguardClockSwitchController.attach(mView.findViewById(R.id.keyguard_clock_container));
+                mKeyguardStatusViewComponentFactory
+                        .build(mKeyguardStatusView)
+                        .getKeyguardClockSwitchController();
+        keyguardClockSwitchController.init();
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
@@ -707,8 +717,6 @@
     }
 
     private void reInflateViews() {
-        updateShowEmptyShadeView();
-
         // Re-inflate the status view group.
         int index = mView.indexOfChild(mKeyguardStatusView);
         mView.removeView(mKeyguardStatusView);
@@ -720,8 +728,10 @@
         // Re-associate the clock container with the keyguard clock switch.
         mBigClockContainer.removeAllViews();
         KeyguardClockSwitchController keyguardClockSwitchController =
-                mKeyguardClockSwitchControllerProvider.get();
-        keyguardClockSwitchController.attach(mView.findViewById(R.id.keyguard_clock_container));
+                mKeyguardStatusViewComponentFactory
+                        .build(mKeyguardStatusView)
+                        .getKeyguardClockSwitchController();
+        keyguardClockSwitchController.init();
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
         // Update keyguard bottom area
@@ -757,6 +767,20 @@
         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
     }
 
+    private void updateMaxDisplayedNotifications(boolean recompute) {
+        if (recompute) {
+            mMaxAllowedKeyguardNotifications = Math.max(computeMaxKeyguardNotifications(), 1);
+        }
+
+        if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
+            mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
+                    mMaxAllowedKeyguardNotifications);
+        } else {
+            // no max when not on the keyguard
+            mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
+        }
+    }
+
     public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
         mKeyguardIndicationController = indicationController;
         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
@@ -817,7 +841,7 @@
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = animate || mAnimateNextPositionUpdate;
         int stackScrollerPadding;
-        if (mBarState != StatusBarState.KEYGUARD) {
+        if (mBarState != KEYGUARD) {
             stackScrollerPadding = getUnlockedStackScrollerPadding();
         } else {
             int totalHeight = mView.getHeight();
@@ -861,20 +885,17 @@
     }
 
     /**
-     * @param maximum the maximum to return at most
      * @return the maximum keyguard notifications that can fit on the screen
      */
-    public int computeMaxKeyguardNotifications(int maximum) {
+    private int computeMaxKeyguardNotifications() {
         float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
         int notificationPadding = Math.max(
                 1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
-        NotificationShelf shelf = mNotificationShelfController.getView();
-        float
-                shelfSize =
-                shelf.getVisibility() == View.GONE ? 0
-                        : shelf.getIntrinsicHeight() + notificationPadding;
-        float
-                availableSpace =
+        float shelfSize =
+                mNotificationShelfController.getVisibility() == View.GONE
+                        ? 0
+                        : mNotificationShelfController.getIntrinsicHeight() + notificationPadding;
+        float availableSpace =
                 mNotificationStackScrollLayoutController.getHeight() - minPadding - shelfSize
                         - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
                         - mKeyguardStatusView.getLogoutButtonHeight();
@@ -904,7 +925,8 @@
             availableSpace -= mNotificationStackScrollLayoutController
                     .calculateGapHeight(previousView, child, count);
             previousView = child;
-            if (availableSpace >= 0 && count < maximum) {
+            if (availableSpace >= 0
+                    && (mMaxKeyguardNotifications == -1 || count < mMaxKeyguardNotifications)) {
                 count++;
             } else if (availableSpace > -shelfSize) {
                 // if we are exactly the last view, then we can show us still!
@@ -1231,7 +1253,7 @@
         float vel = getCurrentQSVelocity();
         final int
                 gesture =
-                mBarState == StatusBarState.KEYGUARD ? MetricsEvent.ACTION_LS_QS
+                mBarState == KEYGUARD ? MetricsEvent.ACTION_LS_QS
                         : MetricsEvent.ACTION_SHADE_QS_PULL;
         mLockscreenGestureLogger.write(gesture,
                 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
@@ -1239,7 +1261,7 @@
     }
 
     private boolean flingExpandsQs(float vel) {
-        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
             return false;
         }
         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -1249,12 +1271,12 @@
         }
     }
 
-    private boolean isFalseTouch() {
+    private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         return !mQsTouchAboveFalsingThreshold;
     }
@@ -1288,7 +1310,7 @@
     private boolean handleQsTouch(MotionEvent event) {
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
-                && mBarState != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
+                && mBarState != KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
 
             // Down in the empty area while fully expanded - go to QS.
             mQsTracking = true;
@@ -1642,7 +1664,7 @@
                     mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
                     Interpolators.ALPHA_OUT).withEndAction(
                     mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
-        } else if (statusBarState == StatusBarState.KEYGUARD
+        } else if (statusBarState == KEYGUARD
                 || statusBarState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
             mKeyguardBottomArea.setAlpha(1f);
@@ -1655,8 +1677,8 @@
             boolean goingToFullShade) {
         mKeyguardStatusView.animate().cancel();
         mKeyguardStatusViewAnimating = false;
-        if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
-                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
+        if ((!keyguardFadingAway && mBarState == KEYGUARD
+                && statusBarState != KEYGUARD) || goingToFullShade) {
             mKeyguardStatusViewAnimating = true;
             mKeyguardStatusView.animate().alpha(0f).setStartDelay(0).setDuration(
                     160).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(
@@ -1667,14 +1689,14 @@
                         mKeyguardStateController.getShortenedFadingAwayDuration()).start();
             }
         } else if (mBarState == StatusBarState.SHADE_LOCKED
-                && statusBarState == StatusBarState.KEYGUARD) {
+                && statusBarState == KEYGUARD) {
             mKeyguardStatusView.setVisibility(View.VISIBLE);
             mKeyguardStatusViewAnimating = true;
             mKeyguardStatusView.setAlpha(0f);
             mKeyguardStatusView.animate().alpha(1f).setStartDelay(0).setDuration(
                     320).setInterpolator(Interpolators.ALPHA_IN).withEndAction(
                     mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == StatusBarState.KEYGUARD) {
+        } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardStatusViewAnimating = true;
                 mKeyguardStatusView.animate().alpha(0).translationYBy(
@@ -1694,9 +1716,8 @@
     private void updateQsState() {
         mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
         mNotificationStackScrollLayoutController.setScrollingEnabled(
-                mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
+                mBarState != KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
-        updateEmptyShadeView();
 
         mQsNavbarScrim.setVisibility(
                 mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
@@ -1721,7 +1742,7 @@
         updateQsExpansion();
         requestScrollerTopPaddingUpdate(false /* animate */);
         updateHeaderKeyguardAlpha();
-        if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == StatusBarState.KEYGUARD) {
+        if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
             updateKeyguardBottomAreaAlpha();
             updateBigClockAlpha();
         }
@@ -1763,7 +1784,7 @@
             // Upon initialisation when we are not layouted yet we don't want to announce that we
             // are fully expanded, hence the != 0.0f check.
             return mResources.getString(R.string.accessibility_desc_quick_settings);
-        } else if (mBarState == StatusBarState.KEYGUARD) {
+        } else if (mBarState == KEYGUARD) {
             return mResources.getString(R.string.accessibility_desc_lock_screen);
         } else {
             return mResources.getString(R.string.accessibility_desc_notification_shade);
@@ -1781,7 +1802,7 @@
             // for a nice motion.
             int maxNotificationPadding = getKeyguardNotificationStaticPadding();
             int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
-            int max = mBarState == StatusBarState.KEYGUARD ? Math.max(
+            int max = mBarState == KEYGUARD ? Math.max(
                     maxNotificationPadding, maxQsPadding) : maxQsPadding;
             return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
                     getExpandedFraction());
@@ -1969,7 +1990,7 @@
     @Override
     protected boolean canCollapsePanelOnTouch() {
         if (!isInSettings()) {
-            return mBarState == StatusBarState.KEYGUARD
+            return mBarState == KEYGUARD
                     || mIsPanelCollapseOnQQS
                     || mNotificationStackScrollLayoutController.isScrolledToBottom();
         } else {
@@ -1979,7 +2000,7 @@
 
     @Override
     protected int getMaxPanelHeight() {
-        if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
+        if (mKeyguardBypassController.getBypassEnabled() && mBarState == KEYGUARD) {
             return getMaxPanelHeightBypass();
         } else {
             return getMaxPanelHeightNonBypass();
@@ -1988,7 +2009,7 @@
 
     private int getMaxPanelHeightNonBypass() {
         int min = mStatusBarMinHeight;
-        if (!(mBarState == StatusBarState.KEYGUARD)
+        if (!(mBarState == KEYGUARD)
                 && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
             int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
             min = Math.max(min, minHeight);
@@ -2091,7 +2112,7 @@
         int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
         maxHeight += mNotificationStackScrollLayoutController.getTopPaddingOverflow();
 
-        if (mBarState == StatusBarState.KEYGUARD) {
+        if (mBarState == KEYGUARD) {
             int
                     minKeyguardPanelBottom =
                     mClockPositionAlgorithm.getExpandedClockPosition()
@@ -2114,7 +2135,7 @@
         // 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 (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
-                && mShowEmptyShadeView) {
+                && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
             notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
@@ -2128,7 +2149,7 @@
             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
         }
         float totalHeight = Math.max(maxQsHeight,
-                mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
+                mBarState == KEYGUARD ? mClockPositionResult.stackScrollerPadding
                         : 0) + notificationHeight
                 + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
         if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
@@ -2147,7 +2168,7 @@
                 && !mHeadsUpManager.hasPinnedHeadsUp()) {
             alpha = getFadeoutAlpha();
         }
-        if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
+        if (mBarState == KEYGUARD && !mHintAnimationRunning
                 && !mKeyguardBypassController.getBypassEnabled()) {
             alpha *= mClockPositionResult.clockAlpha;
         }
@@ -2186,14 +2207,14 @@
      * Hides the header when notifications are colliding with it.
      */
     private void updateHeader() {
-        if (mBarState == StatusBarState.KEYGUARD) {
+        if (mBarState == KEYGUARD) {
             updateHeaderKeyguardAlpha();
         }
         updateQsExpansion();
     }
 
     protected float getHeaderTranslation() {
-        if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
+        if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
             return -mQs.getQsMinExpansionHeight();
         }
         float appearAmount = mNotificationStackScrollLayoutController
@@ -2223,7 +2244,7 @@
      */
     private float getKeyguardContentsAlpha() {
         float alpha;
-        if (mBarState == StatusBarState.KEYGUARD) {
+        if (mBarState == KEYGUARD) {
 
             // When on Keyguard, we hide the header as soon as we expanded close enough to the
             // header
@@ -2372,7 +2393,7 @@
         if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
             return;
         }
-        if (mBarState != StatusBarState.KEYGUARD) {
+        if (mBarState != KEYGUARD) {
             mNotificationStackScrollLayoutController.setOnHeightChangedListener(null);
             if (isPixels) {
                 mNotificationStackScrollLayoutController.setOverScrolledPixels(
@@ -2394,7 +2415,7 @@
             mQsExpandImmediate = true;
             mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
-        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+        if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             mAffordanceHelper.animateHideLeftRightIcon();
         }
         mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -2409,7 +2430,7 @@
                     true /* animate */);
         }
         mNotificationStackScrollLayoutController.onPanelTrackingStopped();
-        if (expand && (mBarState == StatusBarState.KEYGUARD
+        if (expand && (mBarState == KEYGUARD
                 || mBarState == StatusBarState.SHADE_LOCKED)) {
             if (!mHintAnimationRunning) {
                 mAffordanceHelper.reset(true);
@@ -2530,17 +2551,6 @@
         return mDozing;
     }
 
-    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
-        mShowEmptyShadeView = emptyShadeViewVisible;
-        updateEmptyShadeView();
-    }
-
-    private void updateEmptyShadeView() {
-        // Hide "No notifications" in QS.
-        mNotificationStackScrollLayoutController.updateEmptyShadeView(
-                mShowEmptyShadeView && !mQsExpanded);
-    }
-
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
         boolean changed = mQsScrimEnabled != qsScrimEnabled;
         mQsScrimEnabled = qsScrimEnabled;
@@ -2560,7 +2570,7 @@
     @Override
     protected boolean onMiddleClicked() {
         switch (mBarState) {
-            case StatusBarState.KEYGUARD:
+            case KEYGUARD:
                 if (!mDozingOnDown) {
                     if (mKeyguardBypassController.getBypassEnabled()) {
                         mUpdateMonitor.requestFaceAuth();
@@ -2575,7 +2585,7 @@
                 return true;
             case StatusBarState.SHADE_LOCKED:
                 if (!mQsExpanded) {
-                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+                    mStatusBarStateController.setState(KEYGUARD);
                 }
                 return true;
             case StatusBarState.SHADE:
@@ -2746,7 +2756,7 @@
     }
 
     private boolean isOnKeyguard() {
-        return mBarState == StatusBarState.KEYGUARD;
+        return mBarState == KEYGUARD;
     }
 
     public void setPanelScrimMinFraction(float minFraction) {
@@ -2921,7 +2931,7 @@
             mBottomAreaShadeAlphaAnimator.cancel();
         }
 
-        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+        if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             updateDozingVisibilities(animate);
         }
 
@@ -2949,7 +2959,7 @@
     public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
         if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
             mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
-            mStatusBar.updateKeyguardMaxNotifications();
+            updateMaxDisplayedNotifications(true);
         }
     }
 
@@ -3041,32 +3051,40 @@
         if (mKeyguardStatusBar != null) {
             mKeyguardStatusBar.dump(fd, pw, args);
         }
-        if (mKeyguardStatusView != null) {
-            mKeyguardStatusView.dump(fd, pw, args);
-        }
     }
 
     public boolean hasActiveClearableNotifications() {
         return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
-    private void updateShowEmptyShadeView() {
-        boolean
-                showEmptyShadeView =
-                mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
-        showEmptyShadeView(showEmptyShadeView);
-    }
-
     public RemoteInputController.Delegate createRemoteInputDelegate() {
         return mNotificationStackScrollLayoutController.createDelegate();
     }
 
-    void updateNotificationViews(String reason) {
+    /**
+     * Updates the notification views' sections and status bar icons. This is
+     * triggered by the NotificationPresenter whenever there are changes to the underlying
+     * notification data being displayed. In the new notification pipeline, this is handled in
+     * {@link ShadeViewManager}.
+     */
+    public void updateNotificationViews(String reason) {
         mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
         mNotificationStackScrollLayoutController.updateSpeedBumpIndex();
         mNotificationStackScrollLayoutController.updateFooter();
-        updateShowEmptyShadeView();
-        mNotificationStackScrollLayoutController.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() {
@@ -3094,21 +3112,21 @@
         mNotificationStackScrollLayoutController.setScrollingEnabled(b);
     }
 
-    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
-            NotificationShelfController notificationShelfController,
-            NotificationIconAreaController notificationIconAreaController,
-            ScrimController scrimController) {
+    /**
+     * Initialize objects instead of injecting to avoid circular dependencies.
+     */
+    public void initDependencies(
+            StatusBar statusBar,
+            NotificationGroupManager groupManager,
+            NotificationShelfController notificationShelfController) {
         setStatusBar(statusBar);
         setGroupManager(mGroupManager);
         mNotificationStackScrollLayoutController.setNotificationPanelController(this);
-        mNotificationStackScrollLayoutController
-                .setIconAreaController(notificationIconAreaController);
         mNotificationStackScrollLayoutController.setStatusBar(statusBar);
         mNotificationStackScrollLayoutController.setGroupManager(groupManager);
         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
-        mNotificationStackScrollLayoutController.setScrimController(scrimController);
-        updateShowEmptyShadeView();
         mNotificationShelfController = notificationShelfController;
+        updateMaxDisplayedNotifications(true);
     }
 
     public void showTransientIndication(int id) {
@@ -3488,7 +3506,7 @@
 
         @Override
         public boolean needsAntiFalsing() {
-            return mBarState == StatusBarState.KEYGUARD;
+            return mBarState == KEYGUARD;
         }
     }
 
@@ -3503,7 +3521,6 @@
     private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-            mNotificationStackScrollLayoutController.setInHeadsUpPinnedMode(inPinnedMode);
             if (inPinnedMode) {
                 mHeadsUpExistenceChangedRunnable.run();
                 updateNotificationTranslucency();
@@ -3562,20 +3579,8 @@
         }
     }
 
-    private class ZenModeControllerCallback implements ZenModeController.Callback {
-        @Override
-        public void onZenChanged(int zen) {
-            updateShowEmptyShadeView();
-        }
-    }
-
     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
         @Override
-        public void onDensityOrFontScaleChanged() {
-            updateShowEmptyShadeView();
-        }
-
-        @Override
         public void onThemeChanged() {
             final int themeResId = mView.getContext().getThemeResId();
             if (mThemeResId == themeResId) {
@@ -3601,14 +3606,15 @@
             boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
             boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
             int oldState = mBarState;
-            boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
+            boolean keyguardShowing = statusBarState == KEYGUARD;
+
             setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
             setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
 
             mBarState = statusBarState;
             mKeyguardShowing = keyguardShowing;
 
-            if (oldState == StatusBarState.KEYGUARD && (goingToFullShade
+            if (oldState == KEYGUARD && (goingToFullShade
                     || statusBarState == StatusBarState.SHADE_LOCKED)) {
                 animateKeyguardStatusBarOut();
                 long
@@ -3617,7 +3623,7 @@
                                 : mKeyguardStateController.calculateGoingToFullShadeDelay();
                 mQs.animateHeaderSlidingIn(delay);
             } else if (oldState == StatusBarState.SHADE_LOCKED
-                    && statusBarState == StatusBarState.KEYGUARD) {
+                    && statusBarState == KEYGUARD) {
                 animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
                 mNotificationStackScrollLayoutController.resetScrollPosition();
                 // Only animate header if the header is visible. If not, it will partially
@@ -3639,7 +3645,9 @@
             if (keyguardShowing) {
                 updateDozingVisibilities(false /* animate */);
             }
-            // THe update needs to happen after the headerSlide in above, otherwise the translation
+
+            updateMaxDisplayedNotifications(false);
+            // The update needs to happen after the headerSlide in above, otherwise the translation
             // would reset
             updateQSPulseExpansion();
             maybeAnimateBottomAreaAlpha();
@@ -3669,7 +3677,6 @@
         public void onViewAttachedToWindow(View v) {
             FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.addCallback(mStatusBarStateListener);
-            mZenModeController.addCallback(mZenModeControllerCallback);
             mConfigurationController.addCallback(mConfigurationListener);
             mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
             // Theme might have changed between inflating this view and attaching it to the
@@ -3682,7 +3689,6 @@
         public void onViewDetachedFromWindow(View v) {
             FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
-            mZenModeController.removeCallback(mZenModeControllerCallback);
             mConfigurationController.removeCallback(mConfigurationListener);
             mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         }
@@ -3695,6 +3701,7 @@
                 int oldTop, int oldRight, int oldBottom) {
             DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
             super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
+            updateMaxDisplayedNotifications(true);
             setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
             // Update Clock Pivot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 07b0a4b..9ea402d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -43,6 +43,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.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,12 +67,11 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Encapsulates all logic for the notification shade window state management.
  */
-@Singleton
+@SysUISingleton
 public class NotificationShadeWindowControllerImpl implements NotificationShadeWindowController,
         Dumpable, ConfigurationListener {
 
@@ -101,6 +101,7 @@
             mCallbacks = Lists.newArrayList();
 
     private final SysuiColorExtractor mColorExtractor;
+    private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
 
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
@@ -232,6 +233,12 @@
         mScreenBrightnessDoze = value / 255f;
     }
 
+    @Override
+    public void setFaceAuthDisplayBrightness(float brightness) {
+        mFaceAuthDisplayBrightness = brightness;
+        apply(mCurrentState);
+    }
+
     private void setKeyguardDark(boolean dark) {
         int vis = mNotificationShadeView.getSystemUiVisibility();
         if (dark) {
@@ -436,7 +443,7 @@
         if (state.mForceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
-            mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
+            mLpChanged.screenBrightness = mFaceAuthDisplayBrightness;
         }
     }
 
@@ -645,6 +652,7 @@
         pw.println(TAG + ":");
         pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
         pw.println(mCurrentState);
+        mNotificationShadeView.getViewRootImpl().dump("  ", fd, pw, args);
     }
 
     @Override
@@ -701,6 +709,7 @@
         boolean mHeadsUpShowing;
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
+        int mFaceAuthDisplayBrightness;
         boolean mForceUserActivity;
         boolean mLaunchingActivity;
         boolean mBackdropShowing;
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 53cc267..a3d3c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 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.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
@@ -83,6 +84,7 @@
     private final NotificationShadeWindowView mView;
     private final ShadeController mShadeController;
     private final NotificationShadeDepthController mDepthController;
+    private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
     private GestureDetector mGestureDetector;
     private View mBrightnessMirror;
@@ -130,7 +132,8 @@
             NotificationShadeDepthController depthController,
             NotificationShadeWindowView notificationShadeWindowView,
             NotificationPanelViewController notificationPanelViewController,
-            SuperStatusBarViewFactory statusBarViewFactory) {
+            SuperStatusBarViewFactory statusBarViewFactory,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
         mInjectionInflationController = injectionInflationController;
         mCoordinator = coordinator;
         mPulseExpansionHandler = pulseExpansionHandler;
@@ -152,6 +155,7 @@
         mNotificationPanelViewController = notificationPanelViewController;
         mDepthController = depthController;
         mStatusBarViewFactory = statusBarViewFactory;
+        mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
@@ -245,7 +249,7 @@
                     }
                 }
                 if (isDown) {
-                    mStackScrollLayout.closeControlsIfOutsideTouch(ev);
+                    mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
                 }
                 if (mStatusBarStateController.isDozing()) {
                     mService.mDozeScrimController.extendPulse();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index ac329e2..0e72506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
 import static java.lang.Float.isNaN;
 
 import android.animation.Animator;
@@ -41,6 +45,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -397,7 +402,12 @@
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
                 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
             }
-            fling(vel, expand, isFalseTouch(x, y));
+            @Classifier.InteractionType int interactionType = vel > 0
+                    ? QUICK_SETTINGS : (
+                            mKeyguardStateController.canDismissLockScreen()
+                                    ? UNLOCK : BOUNCER_UNLOCK);
+
+            fling(vel, expand, isFalseTouch(x, y, interactionType));
             onTrackingStopped(expand);
             mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
             if (mUpdateFlingOnLayout) {
@@ -492,7 +502,11 @@
             return true;
         }
 
-        if (isFalseTouch(x, y)) {
+        @Classifier.InteractionType int interactionType = vel > 0
+                ? QUICK_SETTINGS : (
+                        mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+
+        if (isFalseTouch(x, y, interactionType)) {
             return true;
         }
         if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -511,12 +525,13 @@
      * @param y the final y-coordinate when the finger was lifted
      * @return whether this motion should be regarded as a false touch
      */
-    private boolean isFalseTouch(float x, float y) {
+    private boolean isFalseTouch(float x, float y,
+            @Classifier.InteractionType int interactionType) {
         if (!mStatusBar.isFalsingThresholdNeeded()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         if (!mTouchAboveFalsingThreshold) {
             return true;
@@ -1325,7 +1340,6 @@
         @Override
         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
                 int oldTop, int oldRight, int oldBottom) {
-            mStatusBar.onPanelLaidOut();
             requestPanelHeightUpdate();
             mHasLayoutedSinceDown = true;
             if (mUpdateFlingOnLayout) {
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..e95cf28 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 {
 
@@ -141,6 +141,8 @@
     private ScrimView mScrimBehind;
     private ScrimView mScrimForBubble;
 
+    private Runnable mScrimBehindChangeRunnable;
+
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
@@ -241,6 +243,11 @@
         mScrimInFront = scrimInFront;
         mScrimForBubble = scrimForBubble;
 
+        if (mScrimBehindChangeRunnable != null) {
+            mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable);
+            mScrimBehindChangeRunnable = null;
+        }
+
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
             states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters,
@@ -934,7 +941,13 @@
     }
 
     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
-        mScrimBehind.setChangeRunnable(changeRunnable);
+        // TODO: remove this. This is necessary because of an order-of-operations limitation.
+        // The fix is to move more of these class into @StatusBarScope
+        if (mScrimBehind == null) {
+            mScrimBehindChangeRunnable = changeRunnable;
+        } else {
+            mScrimBehind.setChangeRunnable(changeRunnable);
+        }
     }
 
     public void setCurrentUser(int currentUser) {
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 921bd4f..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,6 +23,7 @@
 
 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;
@@ -32,12 +33,11 @@
 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 5b251be..5882879 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -144,7 +144,6 @@
 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;
@@ -178,7 +177,7 @@
 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.SplitScreen;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
@@ -204,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;
@@ -389,14 +388,14 @@
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder;
     private final PluginManager mPluginManager;
-    private final Optional<Divider> mDividerOptional;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     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;
@@ -609,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;
@@ -722,7 +721,7 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            Optional<Divider> dividerOptional,
+            Optional<SplitScreen> splitScreenOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
@@ -731,7 +730,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -742,7 +740,8 @@
             DismissCallbackRegistry dismissCallbackRegistry,
             DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -804,7 +803,7 @@
         mRecentsOptional = recentsOptional;
         mStatusBarComponentBuilder = statusBarComponentBuilder;
         mPluginManager = pluginManager;
-        mDividerOptional = dividerOptional;
+        mSplitScreenOptional = splitScreenOptional;
         mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
         mShadeController = shadeController;
         mSuperStatusBarViewFactory = superStatusBarViewFactory;
@@ -812,7 +811,6 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
-        mDarkIconDispatcher = darkIconDispatcher;
         mPluginDependencyProvider = pluginDependencyProvider;
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
@@ -820,6 +818,7 @@
         mIconPolicy = phoneStatusBarPolicy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
         mDemoModeController = demoModeController;
+        mNotificationIconAreaController = notificationIconAreaController;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -953,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);
 
@@ -1037,20 +1039,12 @@
                 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, mDemoModeController);
-        mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
+        updateAodIconArea();
         inflateShelf();
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
-        mNotificationPanelViewController.setOnReinflationListener(
-                mNotificationIconAreaController::initAodIcons);
+        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);
@@ -1163,9 +1157,10 @@
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanelViewController.initDependencies(this, mGroupManager,
-                mNotificationShelfController,
-                mNotificationIconAreaController, mScrimController);
+        mNotificationPanelViewController.initDependencies(
+                this,
+                mGroupManager,
+                mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1279,6 +1274,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() {
@@ -1548,31 +1549,32 @@
         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 (mSplitScreenOptional.isPresent()) {
+            SplitScreen splitScreen = mSplitScreenOptional.get();
+            if (splitScreen.isDividerVisible()) {
+                if (splitScreen.isMinimized()
+                        && !splitScreen.isHomeStackResizable()) {
+                    // Undocking from the minimized state is not supported
+                    return false;
+                } else {
+                    splitScreen.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);
     }
 
     /**
@@ -1847,7 +1849,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");
@@ -2068,7 +2070,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()
@@ -3785,7 +3787,6 @@
             mDeviceInteractive = false;
             mWakeUpComingFromTouch = false;
             mWakeUpTouchLocation = null;
-            mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
@@ -3822,7 +3823,6 @@
             if (!mKeyguardBypassController.getBypassEnabled()) {
                 mHeadsUpManager.releaseAllImmediately();
             }
-            mVisualStabilityManager.setScreenOn(true);
             updateVisibleToUser();
             updateIsKeyguard();
             mDozeServiceHost.stopDozing();
@@ -3916,14 +3916,14 @@
     @Override
     public void appTransitionCancelled(int displayId) {
         if (displayId == mDisplayId) {
-            mDividerOptional.ifPresent(Divider::onAppTransitionFinished);
+            mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.onAppTransitionFinished());
         }
     }
 
     @Override
     public void appTransitionFinished(int displayId) {
         if (displayId == mDisplayId) {
-            mDividerOptional.ifPresent(Divider::onAppTransitionFinished);
+            mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.onAppTransitionFinished());
         }
     }
 
@@ -4221,25 +4221,6 @@
         KeyboardShortcuts.dismiss();
     }
 
-    /**
-     * Called when the notification panel layouts
-     */
-    public void onPanelLaidOut() {
-        updateKeyguardMaxNotifications();
-    }
-
-    public void updateKeyguardMaxNotifications() {
-        if (mState == StatusBarState.KEYGUARD) {
-            // Since the number of notifications is determined based on the height of the view, we
-            // need to update them.
-            int maxBefore = mPresenter.getMaxNotificationsWhileLocked(false /* recompute */);
-            int maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
-            if (maxBefore != maxNotifications) {
-                mViewHierarchyManager.updateRowStates();
-            }
-        }
-    }
-
     public void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone) {
         if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
 
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 60ef523..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,7 @@
 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;
@@ -47,14 +48,13 @@
 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, DemoMode {
 
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 e159fad..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,8 +47,10 @@
 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;
@@ -67,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
@@ -77,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,
@@ -103,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() {
@@ -212,6 +215,7 @@
             DockManager dockManager,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
+            Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
             NotificationMediaManager notificationMediaManager) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -224,6 +228,7 @@
         mKeyguardUpdateManager = keyguardUpdateMonitor;
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
+        mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
     }
 
     @Override
@@ -248,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 b9f94b3..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,6 +49,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
+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;
@@ -72,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;
 
@@ -124,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;
 
@@ -158,7 +158,7 @@
             FeatureFlags featureFlags,
             MetricsLogger metricsLogger,
             StatusBarNotificationActivityStarterLogger logger,
-            OnDismissCallback onDismissCallback,
+            OnUserInteractionCallback onUserInteractionCallback,
 
             StatusBar statusBar,
             NotificationPresenter presenter,
@@ -193,7 +193,7 @@
         mFeatureFlags = featureFlags;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
-        mOnDismissCallback = onDismissCallback;
+        mOnUserInteractionCallback = onUserInteractionCallback;
 
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
@@ -575,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);
             }
         });
     }
@@ -596,7 +596,7 @@
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
-    @Singleton
+    @SysUISingleton
     public static class Builder {
         private final Context mContext;
         private final CommandQueue mCommandQueue;
@@ -628,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;
@@ -666,7 +666,7 @@
                 FeatureFlags featureFlags,
                 MetricsLogger metricsLogger,
                 StatusBarNotificationActivityStarterLogger logger,
-                OnDismissCallback onDismissCallback) {
+                OnUserInteractionCallback onUserInteractionCallback) {
 
             mContext = context;
             mCommandQueue = commandQueue;
@@ -697,7 +697,7 @@
             mFeatureFlags = featureFlags;
             mMetricsLogger = metricsLogger;
             mLogger = logger;
-            mOnDismissCallback = onDismissCallback;
+            mOnUserInteractionCallback = onUserInteractionCallback;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -754,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 0ec22b4..024a0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -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;
@@ -110,7 +110,6 @@
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
     private final ScrimController mScrimController;
-    private final Context mContext;
     private final KeyguardIndicationController mKeyguardIndicationController;
     private final StatusBar mStatusBar;
     private final ShadeController mShadeController;
@@ -119,7 +118,6 @@
     private final AccessibilityManager mAccessibilityManager;
     private final KeyguardManager mKeyguardManager;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final int mMaxAllowedKeyguardNotifications;
     private final IStatusBarService mBarService;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private boolean mReinflateNotificationsOnUserSwitched;
@@ -127,7 +125,6 @@
     private TextView mNotificationPanelDebugText;
 
     protected boolean mVrMode;
-    private int mMaxKeyguardNotifications;
 
     public StatusBarNotificationPresenter(Context context,
             NotificationPanelViewController panel,
@@ -145,7 +142,6 @@
             CommandQueue commandQueue,
             InitController initController,
             NotificationInterruptStateProvider notificationInterruptStateProvider) {
-        mContext = context;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
         mHeadsUpManager = headsUp;
@@ -163,8 +159,6 @@
         mDozeScrimController = dozeScrimController;
         mScrimController = scrimController;
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
-        mMaxAllowedKeyguardNotifications = context.getResources().getInteger(
-                R.integer.keyguard_max_notification_count);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
@@ -217,7 +211,6 @@
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
-            mVisualStabilityManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(this,
                     stackScrollerController.getNotificationListContainer(), mCheckSaveListener,
                     mOnSettingsClickListener);
@@ -398,17 +391,6 @@
     }
 
     @Override
-    public int getMaxNotificationsWhileLocked(boolean recompute) {
-        if (recompute) {
-            mMaxKeyguardNotifications = Math.max(1,
-                    mNotificationPanel.computeMaxKeyguardNotifications(
-                            mMaxAllowedKeyguardNotifications));
-            return mMaxKeyguardNotifications;
-        }
-        return mMaxKeyguardNotifications;
-    }
-
-    @Override
     public void onUpdateRowStates() {
         mNotificationPanel.onUpdateRowStates();
     }
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 ac69d9c..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 {
 
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 b79bb70..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,7 @@
 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;
@@ -41,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 d230eca..72067d3 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,6 +33,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.UiBackground;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -40,13 +41,12 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.plugins.DarkIconDispatcher;
 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.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -61,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;
@@ -81,6 +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.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -105,7 +106,6 @@
 
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 import dagger.Module;
@@ -120,7 +120,7 @@
      * Provides our instance of StatusBar which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static StatusBar provideStatusBar(
             Context context,
             NotificationsController notificationsController,
@@ -180,7 +180,7 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            Optional<Divider> dividerOptional,
+            Optional<SplitScreen> splitScreenOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
@@ -189,7 +189,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -200,7 +199,8 @@
             DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -260,7 +260,7 @@
                 recentsOptional,
                 statusBarComponentBuilder,
                 pluginManager,
-                dividerOptional,
+                splitScreenOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
                 shadeController,
@@ -268,7 +268,6 @@
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
                 initController,
-                darkIconDispatcher,
                 timeTickHandler,
                 pluginDependencyProvider,
                 keyguardDismissUtil,
@@ -279,6 +278,7 @@
                 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 c0caabd..d38284a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -21,13 +21,14 @@
 
 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> {
 
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 b7dc821..57ac85e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -35,6 +35,7 @@
 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;
@@ -47,13 +48,12 @@
 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";
 
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 8110ab4..f39acf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -36,8 +36,10 @@
 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;
+import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -48,16 +50,16 @@
 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";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private final DumpManager mDumpManager;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -77,8 +79,13 @@
     /**
      */
     @Inject
-    public BluetoothControllerImpl(Context context, @Background Looper bgLooper,
-            @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) {
+    public BluetoothControllerImpl(
+            Context context,
+            DumpManager dumpManager,
+            @Background Looper bgLooper,
+            @Main Looper mainLooper,
+            @Nullable LocalBluetoothManager localBluetoothManager) {
+        mDumpManager = dumpManager;
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
         mHandler = new H(mainLooper);
@@ -90,6 +97,7 @@
         }
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mCurrentUser = ActivityManager.getCurrentUser();
+        mDumpManager.registerDumpable(TAG, this);
     }
 
     @Override
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 f38c731..7bde315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -36,6 +36,7 @@
 
 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;
 
@@ -47,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);
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 071f05a..9b4e165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -27,17 +27,17 @@
 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 {
 
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 659212c..d7c2b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -32,18 +32,19 @@
 
 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";
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 1baed09..99feb18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@
 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;
 
@@ -42,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";
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 a995ade..0fdc80b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -40,6 +40,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.util.Utils;
@@ -48,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 {
 
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 7a0f690..2253ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -63,6 +63,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.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -79,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
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 604e76e..272c494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -25,17 +25,18 @@
 
 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 {
 
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 58bb64b..53d68d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -22,14 +22,14 @@
 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 =
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 d1a7a7b..7e54e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -49,6 +49,7 @@
 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;
 
@@ -58,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";
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 cbafedc..20cc46f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -21,16 +21,17 @@
 
 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;
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 6b2820d..9eaee22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -39,15 +39,15 @@
 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";
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..17fcb1d 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;
@@ -206,7 +206,7 @@
             @Override
             protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
                 final SparseArray<Bitmap> bitmaps = params[0];
-                List<UserInfo> infos = mUserManager.getUsers(true);
+                List<UserInfo> infos = mUserManager.getAliveUsers();
                 if (infos == null) {
                     return null;
                 }
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 75c1e33..897a3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -42,6 +42,7 @@
 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;
@@ -53,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";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
new file mode 100644
index 0000000..914105f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.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.statusbar.policy.dagger;
+
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotControllerImpl;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationControllerImpl;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SensorPrivacyController;
+import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+
+import dagger.Binds;
+import dagger.Module;
+
+
+/** Dagger Module for code in the statusbar.policy package. */
+@Module
+public interface StatusBarPolicyModule {
+    /** */
+    @Binds
+    BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    CastController provideCastController(CastControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    ExtensionController provideExtensionController(ExtensionControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    FlashlightController provideFlashlightController(FlashlightControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    KeyguardStateController provideKeyguardMonitor(KeyguardStateControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    HotspotController provideHotspotController(HotspotControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    LocationController provideLocationController(LocationControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    NetworkController provideNetworkController(NetworkControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    NextAlarmController provideNextAlarmController(NextAlarmControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    RotationLockController provideRotationLockController(RotationLockControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    SecurityController provideSecurityController(SecurityControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    SensorPrivacyController provideSensorPrivacyControllerImpl(
+            SensorPrivacyControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
+
+    /** */
+    @Binds
+    ZenModeController provideZenModeController(ZenModeControllerImpl controllerImpl);
+
+}
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/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/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/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 12dced7..d9727a7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -33,6 +33,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
 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;
@@ -45,12 +46,11 @@
 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";
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/tuner/dagger/TunerModule.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
copy to packages/SystemUI/src/com/android/systemui/tuner/dagger/TunerModule.java
index fe5fa2b..faf7b84 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/dagger/TunerModule.java
@@ -14,23 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.onehanded.dagger;
+package com.android.systemui.tuner.dagger;
 
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerServiceImpl;
 
 import dagger.Binds;
 import dagger.Module;
 
-/**
- * Dagger Module for One handed.
- */
+/** Dagger Module for code in the tuner package. */
 @Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
+public interface TunerModule {
+    /** */
     @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
-
+    TunerService provideTunerService(TunerServiceImpl controllerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
index fd6ba1a..df741a0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -16,14 +16,9 @@
 
 package com.android.systemui.tv;
 
-import com.android.systemui.dagger.DefaultComponentBinder;
-import com.android.systemui.dagger.DependencyBinder;
-import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.GlobalModule;
 import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.dagger.SystemServicesModule;
-import com.android.systemui.dagger.SystemUIBinder;
-import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
+import com.android.systemui.dagger.WMModule;
 
 import javax.inject.Singleton;
 
@@ -34,15 +29,10 @@
  */
 @Singleton
 @Component(modules = {
-        DefaultComponentBinder.class,
-        DependencyProvider.class,
-        DependencyBinder.class,
-        OneHandedModule.class,
-        SystemServicesModule.class,
-        SystemUIBinder.class,
-        SystemUIModule.class,
-        TvSystemUIModule.class,
-        TvSystemUIBinder.class})
+        GlobalModule.class,
+        TvSysUIComponentModule.class,
+        WMModule.class
+})
 public interface TvGlobalRootComponent extends GlobalRootComponent {
     /**
      * Component Builder interface. This allows to bind Context instance in the component
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index bc0cb51..bef05eb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.tv;
 
+import com.android.systemui.dagger.DefaultComponentBinder;
+import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.SystemUIBinder;
+import com.android.systemui.dagger.SystemUIModule;
 
 import dagger.Subcomponent;
 
@@ -25,7 +29,13 @@
  * Dagger Subcomponent for Core SysUI.
  */
 @SysUISingleton
-@Subcomponent(modules = {})
+@Subcomponent(modules = {
+        DefaultComponentBinder.class,
+        DependencyProvider.class,
+        SystemUIBinder.class,
+        SystemUIModule.class,
+        TvSystemUIModule.class,
+        TvSystemUIBinder.class})
 public interface TvSysUIComponent extends SysUIComponent {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
similarity index 74%
copy from packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
copy to packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
index 9d05843..334bb01 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings
+package com.android.systemui.tv;
 
-import android.content.ContentResolver
+import dagger.Module;
 
-interface CurrentUserContentResolverProvider {
-
-    val currentUserContentResolver: ContentResolver
-}
\ No newline at end of file
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {TvSysUIComponent.class})
+public abstract class TvSysUIComponentModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 228b2ea..e7c10f1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -27,6 +27,7 @@
 
 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;
@@ -41,7 +42,6 @@
 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;
@@ -62,10 +62,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;
@@ -76,16 +75,14 @@
  * overridden by the System UI implementation.
  */
 @Module(includes = {
-            DividerModule.class,
             QSModule.class,
-            WindowManagerShellModule.class
+            WMShellModule.class
         },
         subcomponents = {
-            TvSysUIComponent.class
         })
 public abstract class TvSystemUIModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     @Nullable
@@ -101,7 +98,7 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static BatteryController provideBatteryController(Context context,
             EnhancedEstimates enhancedEstimates, PowerManager powerManager,
             BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
@@ -113,7 +110,7 @@
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -126,14 +123,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,
@@ -149,7 +146,7 @@
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
             CommandQueue commandQueue) {
         return new Recents(context, recentsImplementation, commandQueue);
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 c82b39a..eb8f065 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,7 +24,7 @@
 import android.view.View;
 
 import com.android.keyguard.KeyguardMessageArea;
-import com.android.keyguard.KeyguardSliceView;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
@@ -38,7 +38,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
@@ -47,7 +46,7 @@
  * 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";
@@ -109,11 +108,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the KeyguardSliceView.
-         */
-        KeyguardSliceView createKeyguardSliceView();
-
-        /**
          * Creates the KeyguardMessageArea.
          */
         KeyguardMessageArea createKeyguardMessageArea();
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/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
new file mode 100644
index 0000000..5946af3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -0,0 +1,70 @@
+/*
+ * 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.concurrency;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for classes found within the concurrent package.
+ */
+@Module
+public abstract class GlobalConcurrencyModule {
+
+    /**
+     * Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
+     */
+    @Binds
+    public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl);
+
+     /** Main Looper */
+    @Provides
+    @Main
+    public static  Looper provideMainLooper() {
+        return Looper.getMainLooper();
+    }
+
+    /**
+     * Main Handler.
+     *
+     * Prefer the Main Executor when possible.
+     */
+    @Provides
+    @Main
+    public static Handler provideMainHandler(@Main Looper mainLooper) {
+        return new Handler(mainLooper);
+    }
+
+    /**
+     * Provide a Main-Thread Executor.
+     */
+    @Provides
+    @Main
+    public static Executor provideMainExecutor(Context context) {
+        return context.getMainExecutor();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
rename to packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index bf22a98..b9b20c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -16,12 +16,12 @@
 
 package com.android.systemui.util.concurrency;
 
-import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
 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,9 +30,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-import javax.inject.Singleton;
-
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -40,10 +37,10 @@
  * Dagger Module for classes found within the concurrent package.
  */
 @Module
-public abstract class ConcurrencyModule {
+public abstract class SysUIConcurrencyModule {
     /** Background Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Looper provideBgLooper() {
         HandlerThread thread = new HandlerThread("SysUiBg",
@@ -54,7 +51,7 @@
 
     /** Long running tasks Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Looper provideLongRunningLooper() {
         HandlerThread thread = new HandlerThread("SysUiLng",
@@ -63,13 +60,6 @@
         return thread.getLooper();
     }
 
-    /** Main Looper */
-    @Provides
-    @Main
-    public static  Looper provideMainLooper() {
-        return Looper.getMainLooper();
-    }
-
     /**
      * Background Handler.
      *
@@ -82,21 +72,10 @@
     }
 
     /**
-     * Main Handler.
-     *
-     * Prefer the Main Executor when possible.
-     */
-    @Provides
-    @Main
-    public static Handler provideMainHandler(@Main Looper mainLooper) {
-        return new Handler(mainLooper);
-    }
-
-    /**
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static Executor provideExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -105,7 +84,7 @@
      * Provide a Long running Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
         return new ExecutorImpl(looper);
@@ -115,26 +94,17 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
 
     /**
-     * Provide a Main-Thread Executor.
-     */
-    @Provides
-    @Main
-    public static Executor provideMainExecutor(Context context) {
-        return context.getMainExecutor();
-    }
-
-    /**
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -143,7 +113,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
@@ -153,7 +123,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
         return new ExecutorImpl(looper);
@@ -163,7 +133,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
     }
@@ -172,7 +142,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static RepeatableExecutor provideBackgroundRepeatableExecutor(
             @Background DelayableExecutor exec) {
@@ -183,7 +153,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
@@ -195,15 +165,9 @@
      * 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();
     }
-
-    /**
-     * Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
-     */
-    @Binds
-    public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
copy to packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index fe5fa2b..cdfa145 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -14,23 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.onehanded.dagger;
+package com.android.systemui.util.dagger;
 
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.RingerModeTrackerImpl;
 
 import dagger.Binds;
 import dagger.Module;
 
-/**
- * Dagger Module for One handed.
- */
+/** Dagger Module for code in the util package. */
 @Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
+public interface UtilModule {
+    /** */
     @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
-
+    RingerModeTracker provideRingerModeTracker(RingerModeTrackerImpl ringerModeTrackerImpl);
 }
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/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 5e72808..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";
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/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 428751b..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,7 @@
 
 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;
@@ -44,12 +45,11 @@
 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{
 
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/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/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
copy to packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index fe5fa2b..1ef4c16 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -14,23 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.systemui.onehanded.dagger;
+package com.android.systemui.volume.dagger;
 
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
+import com.android.systemui.volume.VolumeComponent;
+import com.android.systemui.volume.VolumeDialogComponent;
 
 import dagger.Binds;
 import dagger.Module;
 
-/**
- * Dagger Module for One handed.
- */
+
+/** Dagger Module for code in the volume package. */
 @Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
+public interface VolumeModule {
+    /** */
     @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
-
+    VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
 }
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..e4ff1b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SplitScreen;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.protolog.ShellProtoLogImpl;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+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<SplitScreen> mSplitScreenOptional;
+
+    @Inject
+    WMShell(Context context, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DisplayImeController displayImeController,
+            Optional<SplitScreen> splitScreenOptional) {
+        super(context);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mDisplayImeController = displayImeController;
+        mSplitScreenOptional = splitScreenOptional;
+    }
+
+    @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();
+
+        mSplitScreenOptional.ifPresent(this::initSplitScreen);
+    }
+
+    private void initSplitScreen(SplitScreen splitScreen) {
+        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.
+                splitScreen.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
+                                || !splitScreen.isSplitScreenSupported()) {
+                            return;
+                        }
+
+                        if (splitScreen.isMinimized()) {
+                            splitScreen.onUndockingTask();
+                        }
+                    }
+
+                    @Override
+                    public void onActivityForcedResizable(String packageName, int taskId,
+                            int reason) {
+                        splitScreen.onActivityForcedResizable(packageName, taskId, reason);
+                    }
+
+                    @Override
+                    public void onActivityDismissingDockedStack() {
+                        splitScreen.onActivityDismissingSplitScreen();
+                    }
+
+                    @Override
+                    public void onActivityLaunchOnSecondaryDisplayFailed() {
+                        splitScreen.onActivityLaunchOnSecondaryDisplayFailed();
+                    }
+                });
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        // Handle commands if provided
+        for (int i = 0; i < args.length; i++) {
+            switch (args[i]) {
+                case "enable-text-logging": {
+                    String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
+                    startTextLogging(groups);
+                    pw.println("Starting logging on groups: " + Arrays.toString(groups));
+                    return;
+                }
+                case "disable-text-logging": {
+                    String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
+                    stopTextLogging(groups);
+                    pw.println("Stopping logging on groups: " + Arrays.toString(groups));
+                    return;
+                }
+            }
+        }
+
+        // Dump WMShell stuff here if no commands were handled
+    }
+
+    private void startTextLogging(String... groups) {
+        ShellProtoLogImpl.getSingleInstance().startTextLogging(mContext, groups);
+    }
+
+    private void stopTextLogging(String... groups) {
+        ShellProtoLogImpl.getSingleInstance().stopTextLogging(groups);
+    }
+}
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..0a3172ce
--- /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.SplitScreen;
+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 SplitScreen optionalSplitScreen();
+}
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..6a2ca44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.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.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.stackdivider.SplitScreen;
+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);
+    }
+
+    @SysUISingleton
+    @Provides
+    static SplitScreen provideSplitScreen(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 d2c61cc..0000000
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
+++ /dev/null
@@ -1,78 +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.systemui.pip.phone.PipMenuActivity;
-import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
-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();
-    }
-
-    /** TODO(b/150319024): PipMenuActivity will move to a Window */
-    @Singleton
-    @PipMenuActivityClass
-    @Provides
-    static Class<?> providePipMenuActivityClass() {
-        return PipMenuActivity.class;
-    }
-}
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/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 657e4fb..3aa6ec0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -62,6 +62,8 @@
     private ClockPlugin mClockPlugin;
     @Mock
     ColorExtractor.GradientColors mGradientColors;
+    @Mock
+    KeyguardSliceViewController mKeyguardSliceViewController;
 
     private KeyguardClockSwitchController mController;
 
@@ -69,28 +71,30 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mController = new KeyguardClockSwitchController(
-                mStatusBarStateController, mColorExtractor, mClockManager);
-
         when(mView.isAttachedToWindow()).thenReturn(true);
+
+        mController = new KeyguardClockSwitchController(
+                mView, mStatusBarStateController, mColorExtractor, mClockManager,
+                mKeyguardSliceViewController);
+
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
     }
 
     @Test
-    public void testAttach_viewAlreadyAttached() {
-        mController.attach(mView);
+    public void testInit_viewAlreadyAttached() {
+        mController.init();
 
         verifyAttachment(times(1));
     }
 
     @Test
-    public void testAttach_viewNotYetAttached() {
+    public void testInit_viewNotYetAttached() {
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
 
         when(mView.isAttachedToWindow()).thenReturn(false);
-        mController.attach(mView);
+        mController.init();
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         verifyAttachment(never());
@@ -100,12 +104,17 @@
         verifyAttachment(times(1));
     }
 
+    @Test
+    public void testInitSubControllers() {
+        mController.init();
+        verify(mKeyguardSliceViewController).init();
+    }
 
     @Test
-    public void testAttach_viewDetached() {
+    public void testInit_viewDetached() {
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-        mController.attach(mView);
+        mController.init();
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         verifyAttachment(times(1));
@@ -122,7 +131,7 @@
     public void testBigClockPassesStatusBarState() {
         ViewGroup testView = new FrameLayout(mContext);
 
-        mController.attach(mView);
+        mController.init();
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         mController.setBigClockContainer(testView);
         verify(mView).setBigClockContainer(testView, StatusBarState.SHADE);
@@ -143,7 +152,7 @@
         ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
 
-        mController.attach(mView);
+        mController.init();
         verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
 
         listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 0d52e4f..5999e2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -80,7 +80,7 @@
 
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance()
-                        .getRootComponent()
+                        .getSysUIComponent()
                         .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index aa4db32..559284a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -28,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
@@ -51,6 +53,12 @@
     KeyguardSliceView mMockKeyguardSliceView;
     @Mock
     KeyguardStatusView mMockKeyguardStatusView;
+    @Mock
+    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    @Mock
+    private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
+    @Mock
+    private KeyguardClockSwitchController mKeyguardClockSwitchController;
 
     LayoutInflater mLayoutInflater;
 
@@ -62,11 +70,16 @@
         when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
         when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
         when(mMockKeyguardStatusView.findViewById(R.id.clock)).thenReturn(mMockKeyguardStatusView);
+        when(mKeyguardStatusViewComponentFactory.build(any(KeyguardStatusView.class)))
+                .thenReturn(mKeyguardStatusViewComponent);
+        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
+                .thenReturn(mKeyguardClockSwitchController);
+
         allowTestableLooperAsMainThread();
 
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance()
-                        .getRootComponent()
+                        .getSysUIComponent()
                         .createViewInstanceCreatorFactory());
         mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
         mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -99,7 +112,8 @@
     @Test
     public void testInflation_doesntCrash() {
         KeyguardPresentation keyguardPresentation = new KeyguardPresentation(mContext,
-                mContext.getDisplayNoVerify(), mLayoutInflater);
+                mContext.getDisplayNoVerify(), mKeyguardStatusViewComponentFactory,
+                mLayoutInflater);
         keyguardPresentation.onCreate(null /*savedInstanceState */);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
new file mode 100644
index 0000000..b7bcaa3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceViewControllerTest extends SysuiTestCase {
+    @Mock
+    private KeyguardSliceView mView;;
+    @Mock
+    private KeyguardStatusView mKeyguardStatusView;
+    @Mock
+    private TunerService mTunerService;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    private DumpManager mDumpManager = new DumpManager();
+
+    private KeyguardSliceViewController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mView.getContext()).thenReturn(mContext);
+        mController = new KeyguardSliceViewController(
+                mView, mKeyguardStatusView, mActivityStarter, mConfigurationController,
+                mTunerService, mDumpManager);
+        mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
+    }
+
+    @Test
+    public void refresh_replacesSliceContentAndNotifiesListener() {
+        mController.refresh();
+        verify(mView).hideSlice();
+    }
+
+    @Test
+    public void onAttachedToWindow_registersListeners() {
+        mController.init();
+        verify(mTunerService).addTunable(any(TunerService.Tunable.class), anyString());
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+
+    @Test
+    public void onDetachedFromWindow_unregistersListeners() {
+        ArgumentCaptor<View.OnAttachStateChangeListener> attachListenerArgumentCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+        mController.init();
+        verify(mView).addOnAttachStateChangeListener(attachListenerArgumentCaptor.capture());
+
+        attachListenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
+
+        verify(mTunerService).removeTunable(any(TunerService.Tunable.class));
+        verify(mConfigurationController).removeCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 06552b9..1ab08c2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -15,37 +15,27 @@
  */
 package com.android.keyguard;
 
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.view.View;
 
+import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
+import androidx.slice.widget.RowContent;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
@@ -53,46 +43,18 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardSliceViewTest extends SysuiTestCase {
     private KeyguardSliceView mKeyguardSliceView;
     private Uri mSliceUri;
 
-    @Mock
-    private TunerService mTunerService;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private ActivityStarter mActivityStarter;
-    @Mock
-    private Resources mResources;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        allowTestableLooperAsMainThread();
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
-
-            @Override
-            public View onCreateView(View parent, String name, Context context,
-                    AttributeSet attrs) {
-                return onCreateView(name, context, attrs);
-            }
-
-            @Override
-            public View onCreateView(String name, Context context, AttributeSet attrs) {
-                if ("com.android.keyguard.KeyguardSliceView".equals(name)) {
-                    return new KeyguardSliceView(getContext(), attrs, mActivityStarter,
-                            mConfigurationController, mTunerService, mResources);
-                }
-                return null;
-            }
-        });
         mKeyguardSliceView = (KeyguardSliceView) layoutInflater
                 .inflate(R.layout.keyguard_status_area, null);
-        mKeyguardSliceView.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
         mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
         SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
     }
@@ -100,9 +62,13 @@
     @Test
     public void showSlice_notifiesListener() {
         ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
+        builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!"));
+        Slice slice = builder.build();
+        RowContent rowContent = new RowContent(slice.getItemArray()[0], 0);
+
         AtomicBoolean notified = new AtomicBoolean();
         mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.onChanged(builder.build());
+        mKeyguardSliceView.showSlice(rowContent, Collections.EMPTY_LIST);
         Assert.assertTrue("Listener should be notified about slice changes.",
                 notified.get());
     }
@@ -111,7 +77,7 @@
     public void showSlice_emptySliceNotifiesListener() {
         AtomicBoolean notified = new AtomicBoolean();
         mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.onChanged(null);
+        mKeyguardSliceView.showSlice(null, Collections.EMPTY_LIST);
         Assert.assertTrue("Listener should be notified about slice changes.",
                 notified.get());
     }
@@ -119,24 +85,17 @@
     @Test
     public void hasHeader_readsSliceData() {
         ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
-        mKeyguardSliceView.onChanged(builder.build());
+        mKeyguardSliceView.showSlice(null, Collections.EMPTY_LIST);
         Assert.assertFalse("View should not have a header", mKeyguardSliceView.hasHeader());
 
         builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!"));
-        mKeyguardSliceView.onChanged(builder.build());
+        Slice slice = builder.build();
+        RowContent rowContent = new RowContent(slice.getItemArray()[0], 0);
+        mKeyguardSliceView.showSlice(rowContent, Collections.EMPTY_LIST);
         Assert.assertTrue("View should have a header", mKeyguardSliceView.hasHeader());
     }
 
     @Test
-    public void refresh_replacesSliceContentAndNotifiesListener() {
-        AtomicBoolean notified = new AtomicBoolean();
-        mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
-        mKeyguardSliceView.refresh();
-        Assert.assertTrue("Listener should be notified about slice changes.",
-                notified.get());
-    }
-
-    @Test
     public void getTextColor_whiteTextWhenAOD() {
         // Set text color to red since the default is white and test would always pass
         mKeyguardSliceView.setTextColor(Color.RED);
@@ -147,18 +106,4 @@
         Assert.assertEquals("Should be using AOD text color", Color.WHITE,
                 mKeyguardSliceView.getTextColor());
     }
-
-    @Test
-    public void onAttachedToWindow_registersListeners() {
-        mKeyguardSliceView.onAttachedToWindow();
-        verify(mTunerService).addTunable(eq(mKeyguardSliceView), anyString());
-        verify(mConfigurationController).addCallback(eq(mKeyguardSliceView));
-    }
-
-    @Test
-    public void onDetachedFromWindow_unregistersListeners() {
-        mKeyguardSliceView.onDetachedFromWindow();
-        verify(mTunerService).removeTunable(eq(mKeyguardSliceView));
-        verify(mConfigurationController).removeCallback(eq(mKeyguardSliceView));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 560d581..0431704 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -40,7 +40,8 @@
 public class KeyguardStatusViewTest extends SysuiTestCase {
 
     @Mock
-    KeyguardSliceView mKeyguardSlice;
+    KeyguardSliceViewController mKeyguardSliceViewController;
+
     @Mock
     KeyguardClockSwitch mClockView;
     @InjectMocks
@@ -52,7 +53,7 @@
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance()
-                        .getRootComponent()
+                        .getSysUIComponent()
                         .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
@@ -64,7 +65,7 @@
     @Test
     public void dozeTimeTick_updatesSlice() {
         mKeyguardStatusView.dozeTimeTick();
-        verify(mKeyguardSlice).refresh();
+        verify(mKeyguardSliceViewController).refresh();
     }
 
     @Test
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/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/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index b7c198e..a6dfbbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -38,6 +38,7 @@
 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;
@@ -94,6 +95,11 @@
                 mContext, mController, newValueAnimator());
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+    }
+
     @Test
     public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
         enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
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/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 3df0df6..4936360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -201,7 +201,7 @@
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()
+                new InjectionInflationController(SystemUIFactory.getInstance().getSysUIComponent()
                         .createViewInstanceCreatorFactory()),
                 new NotificationShelfComponent.Builder() {
                     @Override
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/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index c8e0f49..909acea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -69,7 +69,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -125,7 +125,7 @@
     @Mock GlobalActionsPanelPlugin mWalletPlugin;
     @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
     @Mock private Handler mHandler;
-    @Mock private CurrentUserContextTracker mCurrentUserContextTracker;
+    @Mock private UserContextProvider mUserContextProvider;
     private ControlsComponent mControlsComponent;
 
     private TestableLooper mTestableLooper;
@@ -137,7 +137,7 @@
         allowTestableLooperAsMainThread();
 
         when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
-        when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext);
+        when(mUserContextProvider.getUserContext()).thenReturn(mContext);
         mControlsComponent = new ControlsComponent(
                 true,
                 () -> mControlsController,
@@ -176,7 +176,7 @@
                 mSysUiState,
                 mHandler,
                 mControlsComponent,
-                mCurrentUserContextTracker
+                mUserContextProvider
         );
         mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
 
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/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index c874b1f..f6d6f562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -46,7 +47,6 @@
 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;
 
@@ -72,7 +72,7 @@
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
     private @Mock NavigationModeController mNavigationModeController;
-    private @Mock InjectionInflationController mInjectionInflationController;
+    private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -91,7 +91,7 @@
                 () -> mStatusBarKeyguardViewManager,
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
                 mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController,
-                mInjectionInflationController);
+                mKeyguardDisplayManager);
         mViewMediator.start();
     }
 
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 2e794a4..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,27 +62,14 @@
 
     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,
@@ -91,7 +81,7 @@
     @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 457d559..84c1bf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -16,6 +16,7 @@
 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
@@ -24,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
@@ -48,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
@@ -58,20 +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
     @Mock lateinit var activityStarter: ActivityStarter
-    @JvmField @Rule val mockito = MockitoJUnit.rule()
     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, activityStarter,
-                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)
@@ -86,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
@@ -115,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())
@@ -124,90 +152,81 @@
 
     @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
-        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 {}))
 
@@ -219,7 +238,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // THEN the media data is removed
-        assertThat(listener.removedKey!!).isEqualTo(KEY)
+        verify(listener).onMediaDataRemoved(eq(KEY))
     }
 
     @Test
@@ -229,13 +248,12 @@
         mediaDataManager.appsBlockedFromResume = blocked
 
         // and GIVEN that the manager has a notification from that app 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 {}))
 
@@ -246,14 +264,11 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // THEN the entry will stay as a resume control
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
+        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)
@@ -264,7 +279,8 @@
         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)
@@ -273,8 +289,6 @@
 
     @Test
     fun testDismissMedia_listenerCalled() {
-        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.dismissMediaData(KEY, 0L)
@@ -284,26 +298,4 @@
 
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
-
-    /**
-     * 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
-
-        override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-            this.key = key
-            this.oldKey = oldKey
-            this.data = data
-        }
-
-        override fun onMediaDataRemoved(key: String) {
-            removedKey = 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/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 118cffc..da1ec98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -21,6 +21,7 @@
 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
@@ -44,11 +45,11 @@
     fun addPlayingThenRemote() {
         val playerIsPlaying = mock(MediaControlPanel::class.java)
         whenever(playerIsPlaying.isPlaying).thenReturn(true)
-        val dataIsPlaying = createMediaData(LOCAL, !RESUMPTION)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
 
         val playerIsRemote = mock(MediaControlPanel::class.java)
         whenever(playerIsRemote.isPlaying).thenReturn(false)
-        val dataIsRemote = createMediaData(!LOCAL, !RESUMPTION)
+        val dataIsRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
 
         MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
         MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote)
@@ -59,14 +60,15 @@
     }
 
     @Test
+    @Ignore("Flaky")
     fun switchPlayersPlaying() {
         val playerIsPlaying1 = mock(MediaControlPanel::class.java)
         whenever(playerIsPlaying1.isPlaying).thenReturn(true)
-        val dataIsPlaying1 = createMediaData(LOCAL, !RESUMPTION)
+        val dataIsPlaying1 = createMediaData("app1", LOCAL, !RESUMPTION)
 
         val playerIsPlaying2 = mock(MediaControlPanel::class.java)
         whenever(playerIsPlaying2.isPlaying).thenReturn(false)
-        val dataIsPlaying2 = createMediaData(LOCAL, !RESUMPTION)
+        val dataIsPlaying2 = createMediaData("app2", LOCAL, !RESUMPTION)
 
         MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
         MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
@@ -86,23 +88,23 @@
     fun fullOrderTest() {
         val playerIsPlaying = mock(MediaControlPanel::class.java)
         whenever(playerIsPlaying.isPlaying).thenReturn(true)
-        val dataIsPlaying = createMediaData(LOCAL, !RESUMPTION)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
 
         val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
         whenever(playerIsPlayingAndRemote.isPlaying).thenReturn(true)
-        val dataIsPlayingAndRemote = createMediaData(!LOCAL, !RESUMPTION)
+        val dataIsPlayingAndRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
 
         val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
         whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
-        val dataIsStoppedAndLocal = createMediaData(LOCAL, !RESUMPTION)
+        val dataIsStoppedAndLocal = createMediaData("app3", LOCAL, !RESUMPTION)
 
         val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
         whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
-        val dataIsStoppedAndRemote = createMediaData(!LOCAL, !RESUMPTION)
+        val dataIsStoppedAndRemote = createMediaData("app4", !LOCAL, !RESUMPTION)
 
         val playerCanResume = mock(MediaControlPanel::class.java)
         whenever(playerCanResume.isPlaying).thenReturn(false)
-        val dataCanResume = createMediaData(LOCAL, RESUMPTION)
+        val dataCanResume = createMediaData("app5", LOCAL, RESUMPTION)
 
         MediaPlayerData.addMediaPlayer("3", dataIsStoppedAndLocal, playerIsStoppedAndLocal)
         MediaPlayerData.addMediaPlayer("5", dataIsStoppedAndRemote, playerIsStoppedAndRemote)
@@ -116,7 +118,7 @@
             playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote).inOrder()
     }
 
-    private fun createMediaData(isLocalSession: Boolean, resumption: Boolean) =
-        MediaData(0, false, 0, null, null, null, null, null, emptyList(), emptyList<Int>(), "",
+    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/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 2e4d8a7..57dbac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -52,7 +52,7 @@
 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.stackdivider.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -61,13 +61,13 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
-import java.util.Optional;
-
 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
@@ -90,13 +90,13 @@
                         mock(AccessibilityManagerWrapper.class),
                         mock(DeviceProvisionedController.class),
                         mock(MetricsLogger.class),
-                        mock(OverviewProxyService.class), 
+                        mock(OverviewProxyService.class),
                         mock(NavigationModeController.class),
                         mock(StatusBarStateController.class),
                         mock(SysUiState.class),
                         mock(BroadcastDispatcher.class),
                         mock(CommandQueue.class),
-                        mock(Divider.class),
+                        Optional.of(mock(SplitScreen.class)),
                         Optional.of(mock(Recents.class)),
                         () -> mock(StatusBar.class),
                         mock(ShadeController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index a643c2d..389c5a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -68,7 +68,7 @@
 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.stackdivider.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -121,7 +121,6 @@
         mDependency.injectMockDependency(KeyguardStateController.class);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(NavigationBarController.class);
-        mDependency.injectMockDependency(Divider.class);
         mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
         TestableLooper.get(this).runWithLooper(() -> {
             mHandler = new Handler();
@@ -224,7 +223,7 @@
                 mMockSysUiState,
                 mBroadcastDispatcher,
                 mCommandQueue,
-                mock(Divider.class),
+                Optional.of(mock(SplitScreen.class)),
                 Optional.of(mock(Recents.class)),
                 () -> mock(StatusBar.class),
                 mock(ShadeController.class),
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 87%
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 763f6e4..02d587d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedControllerTest.java
@@ -46,9 +46,9 @@
 @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
@@ -70,7 +70,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mDisplay = mContext.getDisplay();
-        mOneHandedManagerImpl = new OneHandedManagerImpl(
+        mOneHandedController = new OneHandedController(
                 getContext(),
                 mCommandQueue,
                 mMockDisplayController,
@@ -87,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);
 
@@ -104,7 +102,7 @@
 
     @Test
     public void testStartOneHanded() {
-        mOneHandedManagerImpl.startOneHanded();
+        mOneHandedController.startOneHanded();
 
         verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
     }
@@ -112,7 +110,7 @@
     @Test
     public void testStopOneHanded() {
         when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
-        mOneHandedManagerImpl.stopOneHanded();
+        mOneHandedController.stopOneHanded();
 
         verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
     }
@@ -124,7 +122,7 @@
 
     @Test
     public void testStopOneHanded_shouldRemoveTimer() {
-        mOneHandedManagerImpl.stopOneHanded();
+        mOneHandedController.stopOneHanded();
 
         verify(mTimeoutHandler).removeTimer();
     }
@@ -132,7 +130,7 @@
     @Test
     public void testUpdateIsEnabled() {
         final boolean enabled = true;
-        mOneHandedManagerImpl.setOneHandedEnabled(enabled);
+        mOneHandedController.setOneHandedEnabled(enabled);
 
         verify(mMockTouchHandler, times(2)).onOneHandedEnabled(enabled);
     }
@@ -140,7 +138,7 @@
     @Test
     public void testUpdateSwipeToNotificationIsEnabled() {
         final boolean enabled = true;
-        mOneHandedManagerImpl.setSwipeToNotificationEnabled(enabled);
+        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 95a230f..756382a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
@@ -30,8 +30,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -48,7 +48,7 @@
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
     @Mock
     CommandQueue mCommandQueue;
     @Mock
@@ -66,7 +66,7 @@
         mTutorialHandler = new OneHandedTutorialHandler(mContext);
         mGestureHandler = Mockito.spy(new OneHandedGestureHandler(
                 mContext, mMockDisplayController, mMockNavigationModeController));
-        mOneHandedManagerImpl = new OneHandedManagerImpl(
+        mOneHandedController = new OneHandedController(
                 getContext(),
                 mCommandQueue,
                 mMockDisplayController,
@@ -93,15 +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);
-        mOneHandedManagerImpl.setSwipeToNotificationEnabled(false);
+        mOneHandedController.setOneHandedEnabled(false);
+        mOneHandedController.setSwipeToNotificationEnabled(false);
 
         assertThat(mGestureHandler.mInputMonitor).isNull();
         assertThat(mGestureHandler.mInputEventReceiver).isNull();
@@ -111,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/OneHandedTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
index 8ae632d..3c3ace0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
@@ -28,8 +28,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -46,7 +46,7 @@
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
     @Mock
     CommandQueue mCommandQueue;
     @Mock
@@ -64,7 +64,7 @@
         mTouchHandler = Mockito.spy(new OneHandedTouchHandler());
         mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
                 mMockNavigationModeController);
-        mOneHandedManagerImpl = new OneHandedManagerImpl(
+        mOneHandedController = new OneHandedController(
                 getContext(),
                 mCommandQueue,
                 mMockDisplayController,
@@ -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 c75a8d2..1bffbf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
@@ -24,8 +24,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -42,7 +42,7 @@
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
     @Mock
     CommandQueue mCommandQueue;
     @Mock
@@ -61,7 +61,7 @@
         mTutorialHandler = Mockito.spy(new OneHandedTutorialHandler(mContext));
         mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
                 mMockNavigationModeController);
-        mOneHandedManagerImpl = new OneHandedManagerImpl(
+        mOneHandedController = new OneHandedController(
                 getContext(),
                 mCommandQueue,
                 mMockDisplayController,
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 f0e713e..ae3df5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
@@ -48,7 +48,7 @@
     OneHandedUI mOneHandedUI;
     ScreenLifecycle mScreenLifecycle;
     @Mock
-    OneHandedManagerImpl mMockOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
     @Mock
     OneHandedTimeoutHandler mMockTimeoutHandler;
 
@@ -59,7 +59,7 @@
         mScreenLifecycle = new ScreenLifecycle();
         mOneHandedUI = new OneHandedUI(mContext,
                 mCommandQueue,
-                mMockOneHandedManagerImpl,
+                mOneHandedController,
                 mScreenLifecycle);
         mOneHandedUI.start();
         mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
@@ -74,14 +74,14 @@
     public void testStartOneHanded() {
         mOneHandedUI.startOneHanded();
 
-        verify(mMockOneHandedManagerImpl).startOneHanded();
+        verify(mOneHandedController).startOneHanded();
     }
 
     @Test
     public void testStopOneHanded() {
         mOneHandedUI.stopOneHanded();
 
-        verify(mMockOneHandedManagerImpl).stopOneHanded();
+        verify(mOneHandedController).stopOneHanded();
     }
 
     @Test
@@ -89,7 +89,7 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.TAPS_APP_TO_EXIT, 1);
 
-        verify(mMockOneHandedManagerImpl).setTaskChangeToExit(true);
+        verify(mOneHandedController).setTaskChangeToExit(true);
     }
 
     @Test
@@ -97,7 +97,7 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, 1);
 
-        verify(mMockOneHandedManagerImpl).setOneHandedEnabled(true);
+        verify(mOneHandedController).setOneHandedEnabled(true);
     }
 
     @Test
@@ -115,7 +115,7 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
 
-        verify(mMockOneHandedManagerImpl).setSwipeToNotificationEnabled(true);
+        verify(mOneHandedController).setSwipeToNotificationEnabled(true);
     }
 
     @Ignore("Clarifying do not receive callback")
@@ -123,14 +123,14 @@
     public void testKeyguardBouncerShowing_shouldStopOneHanded() {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
 
-        verify(mMockOneHandedManagerImpl).stopOneHanded();
+        verify(mOneHandedController).stopOneHanded();
     }
 
     @Test
     public void testScreenTurningOff_shouldStopOneHanded() {
         mScreenLifecycle.dispatchScreenTurningOff();
 
-        verify(mMockOneHandedManagerImpl).stopOneHanded();
+        verify(mOneHandedController).stopOneHanded();
     }
 
 }
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 9f67722..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
@@ -72,9 +72,6 @@
     private InputConsumerController mInputConsumerController;
 
     @Mock
-    private PipBoundsHandler mPipBoundsHandler;
-
-    @Mock
     private PipTaskOrganizer mPipTaskOrganizer;
 
     @Mock
@@ -89,6 +86,7 @@
     @Mock
     private PipUiEventLogger mPipUiEventLogger;
 
+    private PipBoundsHandler mPipBoundsHandler;
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -104,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, mPipUiEventLogger);
+                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/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 9922d36..b46c6ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -152,7 +152,7 @@
                         commandQueue),
                 new InjectionInflationController(
                         SystemUIFactory.getInstance()
-                                .getRootComponent()
+                                .getSysUIComponent()
                                 .createViewInstanceCreatorFactory()),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 4c9e141..3e37fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -33,7 +33,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
 import org.junit.Before;
@@ -62,7 +62,7 @@
     @Mock
     private Executor mExecutor;
     @Mock
-    private CurrentUserContextTracker mUserContextTracker;
+    private UserContextProvider mUserContextTracker;
     private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
         public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
                 boolean requiresShadeOpen) {
@@ -92,7 +92,7 @@
         doNothing().when(mRecordingService).startForeground(anyInt(), any());
         doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
 
-        doReturn(mContext).when(mUserContextTracker).getCurrentUserContext();
+        doReturn(mContext).when(mUserContextTracker).getUserContext();
     }
 
     @Test
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/settings/CurrentUserContextTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
deleted file mode 100644
index 628c06a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
+++ /dev/null
@@ -1,92 +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.settings
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import junit.framework.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class CurrentUserContextTrackerTest : SysuiTestCase() {
-
-    private lateinit var tracker: CurrentUserContextTracker
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        allowTestableLooperAsMainThread()
-
-        // wrap Context so that tests don't throw for missing package errors
-        val wrapped = object : ContextWrapper(context) {
-            override fun createContextAsUser(user: UserHandle, flags: Int): Context {
-                val mockContext = mock(Context::class.java)
-                `when`(mockContext.user).thenReturn(user)
-                `when`(mockContext.userId).thenReturn(user.identifier)
-                return mockContext
-            }
-        }
-
-        tracker = CurrentUserContextTracker(wrapped, broadcastDispatcher)
-        tracker.initialize()
-    }
-
-    @Test
-    fun testContextExistsAfterInit_noCrash() {
-        tracker.currentUserContext
-    }
-
-    @Test
-    fun testUserContextIsCorrectAfterUserSwitch() {
-        // We always start out with system ui test
-        assertTrue("Starting userId should be 0", tracker.currentUserContext.userId == 0)
-
-        // WHEN user changes
-        tracker.handleUserSwitched(1)
-
-        // THEN user context should have the correct userId
-        assertTrue("User has changed to userId 1, the context should reflect that",
-                tracker.currentUserContext.userId == 1)
-    }
-
-    @Suppress("UNUSED_PARAMETER")
-    @Test(expected = IllegalStateException::class)
-    fun testContextTrackerThrowsExceptionWhenNotInitialized() {
-        // GIVEN an uninitialized CurrentUserContextTracker
-        val userTracker = CurrentUserContextTracker(context, broadcastDispatcher)
-
-        // WHEN client asks for a context
-        val userContext = userTracker.currentUserContext
-
-        // THEN an exception is thrown
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
new file mode 100644
index 0000000..f76b50a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UserTrackerImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var context: Context
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock(stubOnly = true)
+    private lateinit var dumpManager: DumpManager
+    @Mock(stubOnly = true)
+    private lateinit var handler: Handler
+
+    private val executor = Executor(Runnable::run)
+    private lateinit var tracker: UserTrackerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(context.userId).thenReturn(UserHandle.USER_SYSTEM)
+        `when`(context.user).thenReturn(UserHandle.SYSTEM)
+        `when`(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
+            val user = invocation.getArgument<UserHandle>(0)
+            `when`(context.user).thenReturn(user)
+            `when`(context.userId).thenReturn(user.identifier)
+            context
+        }
+        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+            val info = UserInfo(invocation.getArgument<Int>(0), "", UserInfo.FLAG_FULL)
+            listOf(info)
+        }
+
+        tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+    }
+
+    @Test
+    fun testNotInitialized() {
+        assertThat(tracker.initialized).isFalse()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserIdBeforeInitThrowsException() {
+        tracker.userId
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserHandleBeforeInitThrowsException() {
+        tracker.userHandle
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserContextBeforeInitThrowsException() {
+        tracker.userContext
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserContentResolverBeforeInitThrowsException() {
+        tracker.userContentResolver
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserProfilesBeforeInitThrowsException() {
+        tracker.userProfiles
+    }
+
+    @Test
+    fun testInitialize() {
+        tracker.initialize(0)
+
+        assertThat(tracker.initialized).isTrue()
+    }
+
+    @Test
+    fun testReceiverRegisteredOnInitialize() {
+        tracker.initialize(0)
+
+        val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
+
+        verify(context).registerReceiverForAllUsers(
+                eq(tracker), capture(captor), isNull(), eq(handler))
+    }
+
+    @Test
+    fun testInitialValuesSet() {
+        val testID = 4
+        tracker.initialize(testID)
+
+        verify(userManager).getProfiles(testID)
+
+        assertThat(tracker.userId).isEqualTo(testID)
+        assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
+        assertThat(tracker.userContext.userId).isEqualTo(testID)
+        assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
+        assertThat(tracker.userProfiles).hasSize(1)
+
+        val info = tracker.userProfiles[0]
+        assertThat(info.id).isEqualTo(testID)
+    }
+
+    @Test
+    fun testUserSwitch() {
+        tracker.initialize(0)
+        val newID = 5
+
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
+        tracker.onReceive(context, intent)
+
+        verify(userManager).getProfiles(newID)
+
+        assertThat(tracker.userId).isEqualTo(newID)
+        assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
+        assertThat(tracker.userContext.userId).isEqualTo(newID)
+        assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
+        assertThat(tracker.userProfiles).hasSize(1)
+
+        val info = tracker.userProfiles[0]
+        assertThat(info.id).isEqualTo(newID)
+    }
+
+    @Test
+    fun testManagedProfileAvailable() {
+        tracker.initialize(0)
+        val profileID = tracker.userId + 10
+
+        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+            val id = invocation.getArgument<Int>(0)
+            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+            val infoProfile = UserInfo(
+                    id + 10,
+                    "",
+                    "",
+                    UserInfo.FLAG_MANAGED_PROFILE,
+                    UserManager.USER_TYPE_PROFILE_MANAGED
+            )
+            infoProfile.profileGroupId = id
+            listOf(info, infoProfile)
+        }
+
+        val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+        tracker.onReceive(context, intent)
+
+        assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+    }
+
+    @Test
+    fun testCallbackNotCalledOnAdd() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+
+        tracker.addCallback(callback, executor)
+
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+        assertThat(callback.calledOnUserChanged).isEqualTo(0)
+    }
+
+    @Test
+    fun testCallbackCalledOnUserChanged() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+
+        val newID = 5
+
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
+        tracker.onReceive(context, intent)
+
+        assertThat(callback.calledOnUserChanged).isEqualTo(1)
+        assertThat(callback.lastUser).isEqualTo(newID)
+        assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
+    }
+
+    @Test
+    fun testCallbackCalledOnProfileChanged() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+        val profileID = tracker.userId + 10
+
+        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+            val id = invocation.getArgument<Int>(0)
+            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+            val infoProfile = UserInfo(
+                    id + 10,
+                    "",
+                    "",
+                    UserInfo.FLAG_MANAGED_PROFILE,
+                    UserManager.USER_TYPE_PROFILE_MANAGED
+            )
+            infoProfile.profileGroupId = id
+            listOf(info, infoProfile)
+        }
+
+        val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+        tracker.onReceive(context, intent)
+
+        assertThat(callback.calledOnUserChanged).isEqualTo(0)
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+    }
+
+    @Test
+    fun testCallbackRemoved() {
+        tracker.initialize(0)
+        val newID = 5
+        val profileID = newID + 10
+
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+        tracker.removeCallback(callback)
+
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5)
+        tracker.onReceive(context, intent)
+
+        val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+        tracker.onReceive(context, intentProfiles)
+
+        assertThat(callback.calledOnUserChanged).isEqualTo(0)
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+    }
+
+    private class TestCallback : UserTracker.Callback {
+        var calledOnUserChanged = 0
+        var calledOnProfilesChanged = 0
+        var lastUser: Int? = null
+        var lastUserContext: Context? = null
+        var lastUserProfiles = emptyList<UserInfo>()
+
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            calledOnUserChanged++
+            lastUser = newUser
+            lastUserContext = userContext
+        }
+
+        override fun onProfilesChanged(profiles: List<UserInfo>) {
+            calledOnProfilesChanged++
+            lastUserProfiles = profiles
+        }
+    }
+}
\ No newline at end of file
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 c35ada7..d2bf483 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,9 @@
 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 +43,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;
@@ -221,7 +218,7 @@
                 mViewHierarchyManager.onDynamicPrivacyChanged();
             }
             return null;
-        }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
+        }).when(mListContainer).onNotificationViewUpdateFinished();
 
         // WHEN we call updateNotificationViews()
         mViewHierarchyManager.updateNotificationViews();
@@ -249,7 +246,7 @@
                 mViewHierarchyManager.onDynamicPrivacyChanged();
             }
             return null;
-        }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
+        }).when(mListContainer).onNotificationViewUpdateFinished();
 
         // WHEN we call updateNotificationViews() and drain the looper
         mViewHierarchyManager.updateNotificationViews();
@@ -264,7 +261,6 @@
     private class FakeListContainer implements NotificationListContainer {
         final LinearLayout mLayout = new LinearLayout(mContext);
         final List<View> mRows = Lists.newArrayList();
-        private boolean mMakeReentrantCallDuringSetMaxDisplayedNotifications;
 
         @Override
         public void setChildTransferInProgress(boolean childTransferInProgress) {}
@@ -324,9 +320,6 @@
 
         @Override
         public void setMaxDisplayedNotifications(int maxNotifications) {
-            if (mMakeReentrantCallDuringSetMaxDisplayedNotifications) {
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-            }
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index d4718e7..fc0201a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -346,7 +346,6 @@
         setSmartActions(mEntry.getKey(), null);
 
         mEntryManager.updateNotificationRanking(mRankingMap);
-        verify(mRow, never()).setEntry(eq(mEntry));
         assertNull(mEntry.getSmartActions());
     }
 
@@ -360,7 +359,6 @@
         setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
 
         mEntryManager.updateNotificationRanking(mRankingMap);
-        verify(mRow, never()).setEntry(eq(mEntry));
         assertEquals(1, mEntry.getSmartActions().size());
         assertEquals("action", mEntry.getSmartActions().get(0).title);
     }
@@ -375,7 +373,6 @@
         setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
 
         mEntryManager.updateNotificationRanking(mRankingMap);
-        verify(mRow, never()).setEntry(eq(mEntry));
         assertEquals(1, mEntry.getSmartActions().size());
         assertEquals("action", mEntry.getSmartActions().get(0).title);
     }
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 ce8ce2e..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
@@ -78,7 +72,7 @@
     private AppOpsCoordinator mAppOpsCoordinator;
     private NotifFilter mForegroundFilter;
     private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
-    private NotifSection mFgsSection;
+    private NotifSectioner mFgsSection;
 
     private FakeSystemClock mClock = new FakeSystemClock();
     private FakeExecutor mExecutor = new FakeExecutor(mClock);
@@ -111,7 +105,7 @@
                 lifetimeExtenderCaptor.capture());
         mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
 
-        mFgsSection = mAppOpsCoordinator.getSection();
+        mFgsSection = mAppOpsCoordinator.getSectioner();
     }
 
     @Test
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/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/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 54ccc4d..e1668ca 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
@@ -69,12 +69,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 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;
@@ -114,7 +113,7 @@
 
     @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 NotificationListContainer mNotificationListContainer;
@@ -129,7 +128,7 @@
     @Mock private ShortcutManager mShortcutManager;
     @Mock private ChannelEditorDialogController mChannelEditorDialogController;
     @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock private CurrentUserContextTracker mContextTracker;
+    @Mock private UserContextProvider mContextTracker;
     @Mock private BubbleController mBubbleController;
     @Mock(answer = Answers.RETURNS_SELF)
     private PriorityOnboardingDialogController.Builder mBuilder;
@@ -143,19 +142,21 @@
         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());
+                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 fa2fa04..df26c5b 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
@@ -406,12 +406,12 @@
 
         entry.setRow(row);
         mIconManager.createIcons(entry);
-        row.setEntry(entry);
 
         mBindPipelineEntryListener.onEntryInit(entry);
         mBindPipeline.manageRow(entry, row);
 
         row.initialize(
+                entry,
                 APP_NAME,
                 entry.getKey(),
                 mock(ExpansionLogger.class),
@@ -424,7 +424,9 @@
                 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 fec4677..1431bce 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
@@ -18,15 +18,12 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertFalse;
 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;
@@ -36,30 +33,22 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.metrics.LogMaker;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.View;
 
 import androidx.test.annotation.UiThreadTest;
 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;
@@ -74,27 +63,23 @@
 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;
@@ -103,7 +88,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;
@@ -132,17 +116,15 @@
     @Mock private EmptyShadeView mEmptyShadeView;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
-    @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;
+    @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
+    @Mock NotificationStackScrollLayoutController mStackScrollLayoutController;
     private NotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@@ -172,8 +154,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),
@@ -211,17 +191,12 @@
         // 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,
+        mStackScrollerInternal = new NotificationStackScrollLayout(
+                getContext(),
+                null,
                 mNotificationRoundnessManager,
                 mock(DynamicPrivacyController.class),
-                mock(SysuiStatusBarStateController.class),
-                mHeadsUpManager,
-                mKeyguardBypassController,
-                mKeyguardMediaController,
-                new FalsingManagerFake(),
-                mLockscreenUserManager,
-                mock(NotificationGutsManager.class),
-                mZenModeController,
+                mStatusBarStateController,
                 mNotificationSectionsManager,
                 mock(ForegroundServiceSectionController.class),
                 mock(ForegroundServiceDismissalFeatureController.class),
@@ -231,15 +206,16 @@
                 mock(NotifCollection.class),
                 mUiEventLoggerFake
         );
-        verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
-        mUserChangedListener = userChangedCaptor.getValue();
+        mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
+                mNotificationSwipeHelper);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelfController(notificationShelfController);
         mStackScroller.setStatusBar(mBar);
-        mStackScroller.setScrimController(mock(ScrimController.class));
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
-        mStackScroller.setIconAreaController(mNotificationIconAreaController);
+        when(mStackScrollLayoutController.getNoticationRoundessManager())
+                .thenReturn(mock(NotificationRoundnessManager.class));
+        mStackScroller.setController(mStackScrollLayoutController);
 
         // Stub out functionality that isn't necessary to test.
         doNothing().when(mBar)
@@ -270,9 +246,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);
     }
@@ -281,9 +256,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);
     }
@@ -292,12 +266,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);
     }
 
@@ -313,12 +285,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);
@@ -437,9 +403,9 @@
     }
 
     @Test
-    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+    public void testReInflatesFooterViews() {
         clearInvocations(mStackScroller);
-        mStackScroller.onDensityOrFontScaleChanged();
+        mStackScroller.reinflateViews();
         verify(mStackScroller).setFooterView(any());
         verify(mStackScroller).setEmptyShadeView(any());
     }
@@ -447,86 +413,22 @@
     @Test
     @UiThreadTest
     public void testSetIsBeingDraggedResetsExposedMenu() {
-        NotificationSwipeHelper swipeActionHelper =
-                (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper();
-        swipeActionHelper.setExposedMenuView(new View(mContext));
         mStackScroller.setIsBeingDragged(true);
-        assertNull(swipeActionHelper.getExposedMenuView());
+        verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
     }
 
     @Test
     @UiThreadTest
     public void testPanelTrackingStartResetsExposedMenu() {
-        NotificationSwipeHelper swipeActionHelper =
-                (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper();
-        swipeActionHelper.setExposedMenuView(new View(mContext));
         mStackScroller.onPanelTrackingStarted();
-        assertNull(swipeActionHelper.getExposedMenuView());
+        verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
     }
 
     @Test
     @UiThreadTest
     public void testDarkModeResetsExposedMenu() {
-        NotificationSwipeHelper swipeActionHelper =
-                (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper();
-        swipeActionHelper.setExposedMenuView(new View(mContext));
         mStackScroller.setHideAmount(0.1f, 0.1f);
-        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));
+        verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
     }
 
     @Test
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..08dd7d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+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.content.res.Resources;
+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.FalsingManager;
+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.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
+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.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
+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;
+    @Mock
+    private FalsingManager mFalsingManager;
+    @Mock
+    private NotificationSectionsManager mNotificationSectionsManager;
+    @Mock
+    private Resources mResources;
+    @Mock(answer = Answers.RETURNS_SELF)
+    private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
+    @Mock
+    private NotificationSwipeHelper mNotificationSwipeHelper;
+    @Mock
+    private StatusBar mStatusBar;
+    @Mock
+    private ScrimController mScrimController;
+
+    @Captor
+    ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+
+    private NotificationStackScrollLayoutController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
+
+        mController = new NotificationStackScrollLayoutController(
+                true,
+                mNotificationGutsManager,
+                mHeadsUpManager,
+                mNotificationRoundnessManager,
+                mTunerService,
+                mDynamicPrivacyController,
+                mConfigurationController,
+                mSysuiStatusBarStateController,
+                mKeyguardMediaController,
+                mKeyguardBypassController,
+                mZenModeController,
+                mColorExtractor,
+                mNotificationLockscreenUserManager,
+                mMetricsLogger,
+                mFalsingManager,
+                mNotificationSectionsManager,
+                mResources,
+                mNotificationSwipeHelperBuilder,
+                mStatusBar,
+                mScrimController
+        );
+
+        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_zenHiding() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        setupShowEmptyShadeViewState(stateListener, true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                true /* visible */,
+
+                true /* notifVisibleInShade */);
+
+        setupShowEmptyShadeViewState(stateListener, false);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                false /* visible */,
+                true /* notifVisibleInShade */);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        setupShowEmptyShadeViewState(stateListener, true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                true /* visible */,
+                false /* notifVisibleInShade */);
+
+        setupShowEmptyShadeViewState(stateListener, false);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        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);
+
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.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(mNotificationSwipeHelperBuilder).setOnMenuEventListener(
+                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(mNotificationSwipeHelperBuilder).setOnMenuEventListener(
+                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));
+    }
+
+    private void setupShowEmptyShadeViewState(
+            StatusBarStateController.StateListener statusBarStateListener,
+            boolean toShow) {
+        if (toShow) {
+            statusBarStateListener.onStateChanged(SHADE);
+            mController.setQsExpanded(false);
+            mController.getView().removeAllViews();
+        } else {
+            statusBarStateListener.onStateChanged(KEYGUARD);
+            mController.setQsExpanded(true);
+            mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
+        }
+    }
+
+    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/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 323d8bd..0faf5d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -35,6 +35,7 @@
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
 
@@ -78,7 +79,8 @@
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
-                SwipeHelper.X, mCallback, mContext, mListener, new FalsingManagerFake()));
+                mContext.getResources(), ViewConfiguration.get(mContext),
+                new FalsingManagerFake(), SwipeHelper.X, mCallback, mListener));
         mView = mock(View.class);
         mEvent = mock(MotionEvent.class);
         mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index a5f4e51..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,6 +36,7 @@
 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;
@@ -45,7 +46,6 @@
 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;
 
@@ -69,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;
@@ -90,6 +89,7 @@
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
+    @Mock private AuthController mAuthController;
 
     @Before
     public void setup() {
@@ -98,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/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 48bf2db..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
@@ -34,9 +34,9 @@
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-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.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
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 18cb667..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,7 +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.verify;
 import static org.mockito.Mockito.when;
 
@@ -29,10 +28,12 @@
 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,28 +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,
-                mDemoModeController);
+        mController = new NotificationIconAreaController(
+                mContext,
+                mStatusBarStateController,
+                mWakeUpCoordinator,
+                mKeyguardBypassController,
+                mNotificationMediaManager,
+                mListener,
+                mDozeParameters,
+                mBubbleController,
+                mDemoModeController,
+                mDarkIconDispatcher,
+                mStatusBarWindowController);
+        mController.setupAodIcons(mAodIcons);
     }
 
     @Test
@@ -100,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 a0c0e79..4413ff3 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
@@ -52,7 +52,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -77,7 +79,6 @@
 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;
 import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
@@ -110,8 +111,6 @@
     @Mock
     private ViewGroup mBigClockContainer;
     @Mock
-    private ScrimController mScrimController;
-    @Mock
     private NotificationIconAreaController mNotificationAreaController;
     @Mock
     private HeadsUpManagerPhone mHeadsUpManager;
@@ -174,8 +173,6 @@
     private KeyguardClockSwitch mKeyguardClockSwitch;
     private PanelViewController.TouchHandler mTouchHandler;
     @Mock
-    private ZenModeController mZenModeController;
-    @Mock
     private ConfigurationController mConfigurationController;
     @Mock
     private MediaHierarchyManager mMediaHiearchyManager;
@@ -186,10 +183,16 @@
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
+    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    @Mock
+    private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
+    @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock
     private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
+    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -217,6 +220,8 @@
         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);
+        when(mView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
                 mDisplayMetrics);
 
@@ -238,7 +243,12 @@
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
                 new FalsingManagerFake());
+        when(mKeyguardStatusViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusViewComponent);
+        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
+                .thenReturn(mKeyguardClockSwitchController);
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
+                mResources,
                 mInjectionInflationController,
                 coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
                 mFalsingManager, mShadeController,
@@ -246,14 +256,17 @@
                 mKeyguardStateController, mStatusBarStateController, mDozeLog,
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
+                mMetricsLogger, mActivityManager, mConfigurationController,
                 flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
-                () -> mKeyguardClockSwitchController,
-                mNotificationStackScrollLayoutController);
-        mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
-                mNotificationShelfController, mNotificationAreaController, mScrimController);
+                mNotificationStackScrollLayoutController,
+                mNotificationAreaController,
+                mKeyguardStatusViewComponentFactory);
+        mNotificationPanelViewController.initDependencies(
+                mStatusBar,
+                mGroupManager,
+                mNotificationShelfController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
 
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 ea57cc9..25af584 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
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 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.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
@@ -85,6 +86,7 @@
     @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
     @Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
+    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
     @Before
     public void setUp() {
@@ -102,7 +104,7 @@
         mController = new NotificationShadeWindowViewController(
                 new InjectionInflationController(
                         SystemUIFactory.getInstance()
-                                .getRootComponent()
+                                .getSysUIComponent()
                                 .createViewInstanceCreatorFactory()),
                 mCoordinator,
                 mPulseExpansionHandler,
@@ -123,11 +125,11 @@
                 mNotificationShadeDepthController,
                 mView,
                 mNotificationPanelViewController,
-                mStatusBarViewFactory);
+                mStatusBarViewFactory,
+                mNotificationStackScrollLayoutController);
         mController.setupExpandedStatusBar();
         mController.setService(mStatusBar, mNotificationShadeWindowController);
         mController.setDragDownHelper(mDragDownHelper);
-
     }
 
     @Test
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 ccc3078..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,6 +43,7 @@
 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;
@@ -58,6 +59,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -89,6 +92,8 @@
     private View mNotificationContainer;
     @Mock
     private KeyguardBypassController mBypassController;
+    @Mock
+    private FaceAuthScreenBrightnessController mFaceAuthScreenBrightnessController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -108,6 +113,7 @@
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
                 mKeyguardStateController,
+                mFaceAuthScreenBrightnessController,
                 mock(NotificationMediaManager.class));
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
                 mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry,
@@ -274,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 a71b10c..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());
@@ -210,7 +210,7 @@
                         mFeatureFlags,
                         mock(MetricsLogger.class),
                         mock(StatusBarNotificationActivityStarterLogger.class),
-                        mOnDismissCallback)
+                        mOnUserInteractionCallback)
                 .setStatusBar(mStatusBar)
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
@@ -267,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
@@ -296,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 e46aa04..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
@@ -50,9 +50,9 @@
 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;
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 8a1d95ea4..87aee3f 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
@@ -96,7 +96,7 @@
 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.SplitScreen;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationListener;
@@ -117,9 +117,9 @@
 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;
@@ -238,7 +238,7 @@
     @Mock private StatusBarComponent.Builder mStatusBarComponentBuilder;
     @Mock private StatusBarComponent mStatusBarComponent;
     @Mock private PluginManager mPluginManager;
-    @Mock private Divider mDivider;
+    @Mock private SplitScreen mSplitScreen;
     @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     @Mock private LightsOutNotifController mLightsOutNotifController;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
@@ -397,7 +397,7 @@
                 Optional.of(mRecents),
                 mStatusBarComponentBuilderProvider,
                 mPluginManager,
-                Optional.of(mDivider),
+                Optional.of(mSplitScreen),
                 mLightsOutNotifController,
                 mStatusBarNotificationActivityStarterBuilder,
                 mShadeController,
@@ -405,7 +405,6 @@
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
                 mInitController,
-                mDarkIconDispatcher,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
                 mKeyguardDismissUtil,
@@ -416,7 +415,8 @@
                 mDismissCallbackRegistry,
                 mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
-                mStatusBarTouchableRegionManager);
+                mStatusBarTouchableRegionManager,
+                mNotificationIconAreaController);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
@@ -434,7 +434,6 @@
         mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
-        mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
         mStatusBar.mBarService = mBarService;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index fbbfa96..3dd36d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -41,6 +41,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +59,7 @@
     private CachedBluetoothDeviceManager mMockDeviceManager;
     private LocalBluetoothAdapter mMockAdapter;
     private TestableLooper mTestableLooper;
+    private DumpManager mMockDumpManager;
     private BluetoothControllerImpl mBluetoothControllerImpl;
 
     private List<CachedBluetoothDevice> mDevices;
@@ -75,8 +77,10 @@
         when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class));
         when(mMockBluetoothManager.getProfileManager())
                 .thenReturn(mock(LocalBluetoothProfileManager.class));
+        mMockDumpManager = mock(DumpManager.class);
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+                mMockDumpManager,
                 mTestableLooper.getLooper(),
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager);
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/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/services/Android.bp b/services/Android.bp
index b348b91..ef52c2a 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -156,10 +156,14 @@
 
 java_library {
     name: "android_system_server_stubs_current",
+    defaults: ["android_stubs_dists_default"],
     srcs: [":services-stubs.sources"],
     installable: false,
     static_libs: ["android_module_lib_stubs_current"],
     sdk_version: "none",
     system_modules: "none",
     java_version: "1.8",
+    dist: {
+        dir: "apistubs/android/system-server",
+    },
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 833aeec..c41f400 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1530,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;
@@ -1571,7 +1571,7 @@
         }
     }
 
-    private static boolean isClientInPackageWhitelist(
+    private static boolean isClientInPackageAllowlist(
             @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
         if (serviceInfo == null) return false;
 
@@ -1590,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));
             }
         }
@@ -1991,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);
@@ -2261,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
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/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
index 070626be..c70dfcc 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -256,6 +256,7 @@
                 return actionMasked;
         }
     }
+
     /**
      * Sends down events to the view hierarchy for all pointers which are not already being
      * delivered i.e. pointers that are not yet injected.
@@ -285,6 +286,74 @@
     }
 
     /**
+     * Sends down events to the view hierarchy for all pointers which are not already being
+     * delivered with original down location. i.e. pointers that are not yet injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) {
+        // Inject the injected pointers.
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        final MotionEvent event = computeEventWithOriginalDown(prototype);
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Do not send event for already delivered pointers.
+            if (!mState.isInjectedPointerDown(pointerId)) {
+                pointerIdBits |= (1 << pointerId);
+                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+                sendMotionEvent(
+                        event,
+                        action,
+                        mState.getLastReceivedEvent(),
+                        pointerIdBits,
+                        policyFlags);
+            }
+        }
+    }
+
+    private MotionEvent computeEventWithOriginalDown(MotionEvent prototype) {
+        final int pointerCount = prototype.getPointerCount();
+        if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) {
+            Slog.w(LOG_TAG, "The pointer count doesn't match the received count.");
+            return MotionEvent.obtain(prototype);
+        }
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+        MotionEvent.PointerProperties[] properties =
+                new MotionEvent.PointerProperties[pointerCount];
+        for (int i = 0; i < pointerCount; ++i) {
+            final int pointerId = prototype.getPointerId(i);
+            final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId);
+            final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId);
+            coords[i] = new MotionEvent.PointerCoords();
+            coords[i].x = x;
+            coords[i].y = y;
+            properties[i] = new MotionEvent.PointerProperties();
+            properties[i].id = pointerId;
+            properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
+        }
+        MotionEvent event =
+                MotionEvent.obtain(
+                        prototype.getDownTime(),
+                        prototype.getEventTime(),
+                        prototype.getAction(),
+                        pointerCount,
+                        properties,
+                        coords,
+                        prototype.getMetaState(),
+                        prototype.getButtonState(),
+                        prototype.getXPrecision(),
+                        prototype.getYPrecision(),
+                        prototype.getDeviceId(),
+                        prototype.getEdgeFlags(),
+                        prototype.getSource(),
+                        prototype.getFlags());
+        return event;
+    }
+
+    /**
+     *
      * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
      * pointers that are injected.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
index 07e8211..5b46cb4 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -294,7 +294,7 @@
                                 + Float.toString(mGestureDetectionThresholdPixels));
             }
             if (getState() == STATE_CLEAR) {
-                if (moveDelta < mTouchSlop) {
+                if (moveDelta < (mTargetFingerCount * mTouchSlop)) {
                     // This still counts as a touch not a swipe.
                     continue;
                 } else if (mStrokeBuffers[pointerIndex].size() == 0) {
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 a2d58c8..8305be3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -561,24 +561,6 @@
             // stream consistent.
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         }
-        if (mGestureDetector.isMultiFingerGesturesEnabled()
-                && mGestureDetector.isTwoFingerPassthroughEnabled()) {
-            if (event.getPointerCount() == 3) {
-                boolean isOnBottomEdge = false;
-                // If three fingers go 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 = true;
-                    }
-                }
-                if (isOnBottomEdge) {
-                    mState.startDelegating();
-                    mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
-                }
-            }
-        }
     }
 
     /**
@@ -612,15 +594,23 @@
                     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;
+                    // Require both fingers to have moved a certain amount before starting a drag.
+                    for (int index = 0; index < event.getPointerCount(); ++index) {
+                        int id = event.getPointerId(index);
+                        if (!mReceivedPointerTracker.isReceivedPointerDown(id)) {
+                            // Something is wrong with the event stream.
+                            Slog.e(LOG_TAG, "Invalid pointer id: " + id);
+                        }
+                        final float deltaX =
+                                mReceivedPointerTracker.getReceivedPointerDownX(id)
+                                        - rawEvent.getX(index);
+                        final float deltaY =
+                                mReceivedPointerTracker.getReceivedPointerDownY(id)
+                                        - rawEvent.getY(index);
+                        final double moveDelta = Math.hypot(deltaX, deltaY);
+                        if (moveDelta < (2 * mTouchSlop)) {
+                            return;
+                        }
                     }
                 }
                 // More than one pointer so the user is not touch exploring
@@ -630,12 +620,20 @@
                 if (isDraggingGesture(event)) {
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
-                    mState.startDragging();
                     computeDraggingPointerIdIfNeeded(event);
                     pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
-                    mDispatcher.sendMotionEvent(
-                            event, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                    MotionEvent downEvent = computeDownEventForDrag(event);
+                    if (downEvent != null) {
+                        mDispatcher.sendMotionEvent(
+                                downEvent, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_MOVE, rawEvent, pointerIdBits, policyFlags);
+                    } else {
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                    }
+                    mState.startDragging();
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
@@ -644,12 +642,31 @@
                 break;
             default:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()) {
-                    return;
+                    if (mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                        if (event.getPointerCount() == 3) {
+                            // If three fingers went down on the bottom edge of the screen, delegate
+                            // immediately.
+                            if (allPointersDownOnBottomEdge(event)) {
+                                if (DEBUG) {
+                                    Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
+                                }
+                                mState.startDelegating();
+                                if (mState.isTouchExploring()) {
+                                    mDispatcher.sendDownForAllNotInjectedPointers(event,
+                                            policyFlags);
+                                } else {
+                                    mDispatcher.sendDownForAllNotInjectedPointersWithOriginalDown(
+                                            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;
         }
     }
@@ -1000,6 +1017,65 @@
         return distance;
     }
 
+    /**
+     * Creates a down event using the down coordinates of the dragging pointer and other information
+     * from the supplied event. The supplied event's down time is adjusted to reflect the time when
+     * the dragging pointer initially went down.
+     */
+    private MotionEvent computeDownEventForDrag(MotionEvent event) {
+        // Creating a down event only  makes sense if we haven't started touch exploring yet.
+        if (mState.isTouchExploring()
+                || mDraggingPointerId == INVALID_POINTER_ID
+                || event == null) {
+            return null;
+        }
+        final float x = mReceivedPointerTracker.getReceivedPointerDownX(mDraggingPointerId);
+        final float y = mReceivedPointerTracker.getReceivedPointerDownY(mDraggingPointerId);
+        final long time = mReceivedPointerTracker.getReceivedPointerDownTime(mDraggingPointerId);
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+        coords[0] = new MotionEvent.PointerCoords();
+        coords[0].x = x;
+        coords[0].y = y;
+        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+        properties[0] = new MotionEvent.PointerProperties();
+        properties[0].id = mDraggingPointerId;
+        properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
+        MotionEvent downEvent =
+                MotionEvent.obtain(
+                        time,
+                        time,
+                        ACTION_DOWN,
+                        1,
+                        properties,
+                        coords,
+                        event.getMetaState(),
+                        event.getButtonState(),
+                        event.getXPrecision(),
+                        event.getYPrecision(),
+                        event.getDeviceId(),
+                        event.getEdgeFlags(),
+                        event.getSource(),
+                        event.getFlags());
+        event.setDownTime(time);
+        return downEvent;
+    }
+
+    private boolean allPointersDownOnBottomEdge(MotionEvent event) {
+        final long screenHeight =
+                mContext.getResources().getDisplayMetrics().heightPixels;
+        for (int i = 0; i < event.getPointerCount(); ++i) {
+            final int pointerId = event.getPointerId(i);
+            final float pointerDownY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId);
+            if (pointerDownY < (screenHeight - mEdgeSwipeHeightPixels)) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, "The pointer is not on the bottom edge" + pointerDownY);
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
     public TouchState getState() {
         return mState;
     }
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 3ee5b28..7d6067c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -176,7 +176,7 @@
             Slog.i(LOG_TAG, "onDestroy(); delayed = "
                     + mDetectingState.toString());
         }
-        mWindowMagnificationMgr.disableWindowMagnifier(mDisplayId, true);
+        mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true);
         resetToDetectState();
     }
 
@@ -211,14 +211,14 @@
         final float scale = MathUtils.constrain(
                 mWindowMagnificationMgr.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
-        mWindowMagnificationMgr.enableWindowMagnifier(mDisplayId, scale, centerX, centerY);
+        mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY);
     }
 
     private void disableWindowMagnifier() {
         if (DEBUG_ALL) {
             Slog.i(LOG_TAG, "disableWindowMagnifier()");
         }
-        mWindowMagnificationMgr.disableWindowMagnifier(mDisplayId, false);
+        mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, false);
     }
 
     private void toggleMagnification(float centerX, float centerY) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index ed2b26f..ecbece6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -73,7 +73,7 @@
         public void onReceive(Context context, Intent intent) {
             final int displayId = context.getDisplayId();
             removeMagnificationButton(displayId);
-            disableWindowMagnification(displayId);
+            disableWindowMagnification(displayId, false);
         }
     };
 
@@ -136,10 +136,10 @@
     /**
      * Requests {@link IWindowMagnificationConnection} through
      * {@link StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)} and
-     * destroys all window magnifiers if necessary.
+     * destroys all window magnifications if necessary.
      *
      * @param connect {@code true} if needs connection, otherwise set the connection to null and
-     *                            destroy all window magnifiers.
+     *                            destroy all window magnifications.
      * @return {@code true} if {@link IWindowMagnificationConnection} state is going to change.
      */
     public boolean requestConnection(boolean connect) {
@@ -171,7 +171,7 @@
     private void disableAllWindowMagnifiers() {
         for (int i = 0; i < mWindowMagnifiers.size(); i++) {
             final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
-            magnifier.disable();
+            magnifier.disableWindowMagnificationInternal();
         }
         mWindowMagnifiers.clear();
     }
@@ -187,12 +187,12 @@
 
     @Override
     public boolean processScroll(int displayId, float distanceX, float distanceY) {
-        moveWindowMagnifier(displayId, -distanceX, -distanceY);
+        moveWindowMagnification(displayId, -distanceX, -distanceY);
         return /* event consumed: */ true;
     }
 
     /**
-     * Scales the magnified region on the specified display if the window magnifier is enabled.
+     * Scales the magnified region on the specified display if the window magnifier is initiated.
      *
      * @param displayId The logical display id.
      * @param scale The target scale, must be >= 1
@@ -209,7 +209,7 @@
     }
 
     /**
-     * Enables the window magnifier with specified center and scale on the specified display.
+     * Enables window magnification with specified center and scale on the specified display.
      *  @param displayId The logical display id.
      * @param scale The target scale, must be >= 1.
      * @param centerX The screen-relative X coordinate around which to center,
@@ -217,29 +217,29 @@
      * @param centerY The screen-relative Y coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
      */
-    void enableWindowMagnifier(int displayId, float scale, float centerX, float centerY) {
+    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
                 magnifier = createWindowMagnifier(displayId);
             }
-            magnifier.enable(scale, centerX, centerY);
+            magnifier.enableWindowMagnificationInternal(scale, centerX, centerY);
         }
     }
 
     /**
-     * Disables the window magnifier on the specified display.
+     * Disables window magnification on the specified display.
      *
      * @param displayId The logical display id.
      * @param clear {@true} Clears the state of the window magnifier
      */
-    void disableWindowMagnifier(int displayId, boolean clear) {
+    void disableWindowMagnification(int displayId, boolean clear) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
                 return;
             }
-            magnifier.disable();
+            magnifier.disableWindowMagnificationInternal();
             if (clear) {
                 mWindowMagnifiers.delete(displayId);
             }
@@ -264,10 +264,10 @@
     }
 
     /**
-     * Indicates whether this window magnifier is enabled on specified display.
+     * Indicates whether window magnification is enabled on specified display.
      *
      * @param displayId The logical display id.
-     * @return {@code true} if the window magnifier is enabled.
+     * @return {@code true} if the window magnification is enabled.
      */
     boolean isWindowMagnifierEnabled(int displayId) {
         synchronized (mLock) {
@@ -323,7 +323,7 @@
     }
 
     /**
-     * Moves the window magnifier with specified offset.
+     * Moves window magnification on the specified display with the specified offset.
      *
      * @param displayId The logical display id.
      * @param offsetX the amount in pixels to offset the region in the X direction, in current
@@ -331,7 +331,7 @@
      * @param offsetY the amount in pixels to offset the region in the Y direction, in current
      *                screen pixels.
      */
-    void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+    void moveWindowMagnification(int displayId, float offsetX, float offsetY) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
@@ -425,7 +425,8 @@
     }
 
     /**
-     * A class to  manipulate the window magnifier and contains the relevant information.
+     * A class manipulates window magnification per display and contains the magnification
+     * information.
      */
     private static class WindowMagnifier {
 
@@ -434,7 +435,7 @@
         private boolean mEnabled;
 
         private final WindowMagnificationManager mWindowMagnificationManager;
-        //Records the bounds of window magnifier.
+        //Records the bounds of window magnification.
         private final Rect mBounds = new Rect();
         //The magnified bounds on the screen.
         private final Rect mSourceBounds = new Rect();
@@ -444,12 +445,12 @@
         }
 
         @GuardedBy("mLock")
-        void enable(float scale, float centerX, float centerY) {
+        void enableWindowMagnificationInternal(float scale, float centerX, float centerY) {
             if (mEnabled) {
                 return;
             }
             final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-            if (mWindowMagnificationManager.enableWindowMagnification(mDisplayId, normScale,
+            if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
                     centerX, centerY)) {
                 mScale = normScale;
                 mEnabled = true;
@@ -457,8 +458,9 @@
         }
 
         @GuardedBy("mLock")
-        void disable() {
-            if (mEnabled && mWindowMagnificationManager.disableWindowMagnification(mDisplayId)) {
+        void disableWindowMagnificationInternal() {
+            if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal(
+                    mDisplayId)) {
                 mEnabled = false;
             }
         }
@@ -519,7 +521,7 @@
         }
     }
 
-    private boolean enableWindowMagnification(int displayId, float scale, float centerX,
+    private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX,
             float centerY) {
         return mConnectionWrapper != null && mConnectionWrapper.enableWindowMagnification(
                 displayId, scale, centerX, centerY);
@@ -529,7 +531,7 @@
         return mConnectionWrapper != null && mConnectionWrapper.setScale(displayId, scale);
     }
 
-    private boolean disableWindowMagnification(int displayId) {
+    private boolean disableWindowMagnificationInternal(int displayId) {
         return mConnectionWrapper != null && mConnectionWrapper.disableWindowMagnification(
                 displayId);
     }
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 1c4db12..59ba82e 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.util.Slog;
@@ -108,9 +109,9 @@
 
         @Override
         public void createPredictionSession(@NonNull AppPredictionContext context,
-                @NonNull AppPredictionSessionId sessionId) {
-            runForUserLocked("createPredictionSession", sessionId,
-                    (service) -> service.onCreatePredictionSessionLocked(context, sessionId));
+                @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+            runForUserLocked("createPredictionSession", sessionId, (service) ->
+                    service.onCreatePredictionSessionLocked(context, sessionId, token));
         }
 
         @Override
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 7ee607c..735f420 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
@@ -44,8 +45,6 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 import com.android.server.people.PeopleServiceInternal;
 
-import java.util.function.Consumer;
-
 /**
  * Per-user instance of {@link AppPredictionManagerService}.
  */
@@ -112,17 +111,24 @@
      */
     @GuardedBy("mLock")
     public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
-            @NonNull AppPredictionSessionId sessionId) {
-        if (!mSessionInfos.containsKey(sessionId)) {
-            mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
-                    DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
-                            PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
-                    this::removeAppPredictionSessionInfo));
-        }
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onCreatePredictionSession(context, sessionId), true);
-        if (!serviceExists) {
-            mSessionInfos.remove(sessionId);
+            @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+        final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+        final boolean serviceExists = resolveService(sessionId, false,
+                usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
+        if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+            final AppPredictionSessionInfo sessionInfo = new AppPredictionSessionInfo(
+                    sessionId, context, usesPeopleService, token, () -> {
+                synchronized (mLock) {
+                    onDestroyPredictionSessionLocked(sessionId);
+                }
+            });
+            if (sessionInfo.linkToDeath()) {
+                mSessionInfos.put(sessionId, sessionInfo);
+            } else {
+                // destroy the session if calling process is already dead
+                onDestroyPredictionSessionLocked(sessionId);
+            }
         }
     }
 
@@ -132,7 +138,10 @@
     @GuardedBy("mLock")
     public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull AppTargetEvent event) {
-        resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyAppTargetEvent(sessionId, event));
     }
 
     /**
@@ -141,8 +150,10 @@
     @GuardedBy("mLock")
     public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
-        resolveService(sessionId, s ->
-                s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
     }
 
     /**
@@ -151,7 +162,10 @@
     @GuardedBy("mLock")
     public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
-        resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.sortAppTargets(sessionId, targets, callback));
     }
 
     /**
@@ -160,10 +174,12 @@
     @GuardedBy("mLock")
     public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.registerPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.registerPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.addCallbackLocked(callback);
         }
     }
@@ -174,10 +190,12 @@
     @GuardedBy("mLock")
     public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.unregisterPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.unregisterPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.removeCallbackLocked(callback);
         }
     }
@@ -187,7 +205,10 @@
      */
     @GuardedBy("mLock")
     public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
-        resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.requestPredictionUpdate(sessionId));
     }
 
     /**
@@ -195,12 +216,14 @@
      */
     @GuardedBy("mLock")
     public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onDestroyPredictionSession(sessionId), false);
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
-            sessionInfo.destroy();
+        if (isDebug()) {
+            Slog.d(TAG, "onDestroyPredictionSessionLocked(): sessionId=" + sessionId);
         }
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.onDestroyPredictionSession(sessionId));
+        sessionInfo.destroy();
     }
 
     @Override
@@ -291,27 +314,18 @@
         }
 
         for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) {
-            sessionInfo.resurrectSessionLocked(this);
-        }
-    }
-
-    private void removeAppPredictionSessionInfo(AppPredictionSessionId sessionId) {
-        if (isDebug()) {
-            Slog.d(TAG, "removeAppPredictionSessionInfo(): sessionId=" + sessionId);
-        }
-        synchronized (mLock) {
-            mSessionInfos.remove(sessionId);
+            sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
         }
     }
 
     @GuardedBy("mLock")
     @Nullable
-    protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId,
-            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb,
-            boolean sendImmediately) {
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (sessionInfo == null) return false;
-        if (sessionInfo.mUsesPeopleService) {
+    protected boolean resolveService(
+            @NonNull final AppPredictionSessionId sessionId,
+            boolean sendImmediately,
+            boolean usesPeopleService,
+            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) {
+        if (usesPeopleService) {
             final IPredictionService service =
                     LocalServices.getService(PeopleServiceInternal.class);
             if (service != null) {
@@ -368,7 +382,9 @@
         private final AppPredictionContext mPredictionContext;
         private final boolean mUsesPeopleService;
         @NonNull
-        private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction;
+        final IBinder mToken;
+        @NonNull
+        final IBinder.DeathRecipient mDeathRecipient;
 
         private final RemoteCallbackList<IPredictionCallback> mCallbacks =
                 new RemoteCallbackList<IPredictionCallback>() {
@@ -388,14 +404,16 @@
                 @NonNull final AppPredictionSessionId id,
                 @NonNull final AppPredictionContext predictionContext,
                 final boolean usesPeopleService,
-                @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) {
+                @NonNull final IBinder token,
+                @NonNull final IBinder.DeathRecipient deathRecipient) {
             if (DEBUG) {
                 Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id);
             }
             mSessionId = id;
             mPredictionContext = predictionContext;
             mUsesPeopleService = usesPeopleService;
-            mRemoveSessionInfoAction = removeSessionInfoAction;
+            mToken = token;
+            mDeathRecipient = deathRecipient;
         }
 
         void addCallbackLocked(IPredictionCallback callback) {
@@ -414,23 +432,38 @@
             mCallbacks.unregister(callback);
         }
 
+        boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+                            + mSessionId);
+                }
+                return false;
+            }
+            return true;
+        }
+
         void destroy() {
             if (DEBUG) {
                 Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
                         + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
             }
+            if (mToken != null) {
+                mToken.unlinkToDeath(mDeathRecipient, 0);
+            }
             mCallbacks.kill();
-            mRemoveSessionInfoAction.accept(mSessionId);
         }
 
-        void resurrectSessionLocked(AppPredictionPerUserService service) {
+        void resurrectSessionLocked(AppPredictionPerUserService service, IBinder token) {
             int callbackCount = mCallbacks.getRegisteredCallbackCount();
             if (DEBUG) {
                 Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
                         + ") for session Id=" + mSessionId + " and "
                         + callbackCount + " callbacks.");
             }
-            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId);
+            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId, token);
             mCallbacks.broadcast(
                     callback -> service.registerPredictionUpdatesLocked(mSessionId, callback));
         }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index ad1986a6..cf9324c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -302,24 +302,11 @@
             String packageName, int userId);
 
     /**
-     * Do a straight uid lookup for the given package/application in the given user. This enforces
-     * app visibility rules and permissions. Call {@link #getPackageUidInternal} for the internal
-     * implementation.
-     * @deprecated Use {@link PackageManager#getPackageUid(String, int)}
-     * @return The app's uid, or < 0 if the package was not found in that user
-     */
-    @Deprecated
-    public abstract int getPackageUid(String packageName,
-            @PackageInfoFlags int flags, int userId);
-
-    /**
      * Do a straight uid lookup for the given package/application in the given user.
      * @see PackageManager#getPackageUidAsUser(String, int, int)
      * @return The app's uid, or < 0 if the package was not found in that user
-     * TODO(b/148235092): rename this to getPackageUid
      */
-    public abstract int getPackageUidInternal(String packageName,
-            @PackageInfoFlags int flags, int userId);
+    public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags, int userId);
 
     /**
      * Retrieve all of the information we know about a particular package/application.
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 97f3b37..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;
@@ -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
@@ -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,7 +1822,7 @@
 
     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 {
@@ -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;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d1d9c0e..27c5d4a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1141,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
@@ -1540,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
@@ -2155,7 +2160,7 @@
             Slog.i(TAG, "Remounting storage for pid: " + pid);
             final String[] sharedPackages =
                     mPmInternal.getSharedUserPackagesForPackage(packageName, userId);
-            final int uid = mPmInternal.getPackageUidInternal(packageName, 0, userId);
+            final int uid = mPmInternal.getPackageUid(packageName, 0 /* flags */, userId);
             final String[] packages =
                     sharedPackages.length != 0 ? sharedPackages : new String[]{packageName};
             try {
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index df966c1..34e6370 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -47,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;
 
@@ -250,76 +250,76 @@
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
     }
 
-    private @NonNull TargetUser getTargetUser(@UserIdInt int userHandle) {
+    private @NonNull TargetUser getTargetUser(@UserIdInt int userId) {
         final TargetUser targetUser;
         synchronized (mTargetUsers) {
-            targetUser = mTargetUsers.get(userHandle);
+            targetUser = mTargetUsers.get(userId);
         }
-        Preconditions.checkState(targetUser != null, "No TargetUser for " + userHandle);
+        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) {
+    public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
         // Create cached TargetUser
-        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userHandle);
-        Preconditions.checkState(userInfo != null, "No UserInfo for " + userHandle);
+        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
+        Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
         synchronized (mTargetUsers) {
-            mTargetUsers.put(userHandle, new TargetUser(userInfo));
+            mTargetUsers.put(userId, new TargetUser(userInfo));
         }
 
-        onUser(t, START, userHandle);
+        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(userHandle);
+            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,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 6d71c8e..eb8308b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -315,11 +315,10 @@
     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
@@ -376,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) {
@@ -909,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]);
                             }
@@ -964,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) {
@@ -1518,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
@@ -1797,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
@@ -2103,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
@@ -2544,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;
@@ -2750,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);
             }
@@ -2796,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) {
@@ -2862,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]);
                 }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 96d973e..687af10 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -85,6 +85,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class VibratorService extends IVibratorService.Stub
         implements InputManager.InputDeviceListener {
@@ -114,6 +115,9 @@
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
 
+    // Used to generate globally unique vibration ids.
+    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
     // 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
@@ -171,34 +175,34 @@
     private int mRingIntensity;
     private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
 
-    static native long vibratorInit();
+    static native long vibratorInit(OnCompleteListener listener);
 
     static native long vibratorGetFinalizer();
 
-    static native boolean vibratorExists(long controllerPtr);
+    static native boolean vibratorExists(long nativeServicePtr);
 
-    static native void vibratorOn(long controllerPtr, long milliseconds, Vibration vibration);
+    static native void vibratorOn(long nativeServicePtr, long milliseconds, long vibrationId);
 
-    static native void vibratorOff(long controllerPtr);
+    static native void vibratorOff(long nativeServicePtr);
 
-    static native void vibratorSetAmplitude(long controllerPtr, int amplitude);
+    static native void vibratorSetAmplitude(long nativeServicePtr, int amplitude);
 
-    static native int[] vibratorGetSupportedEffects(long controllerPtr);
+    static native int[] vibratorGetSupportedEffects(long nativeServicePtr);
 
-    static native int[] vibratorGetSupportedPrimitives(long controllerPtr);
+    static native int[] vibratorGetSupportedPrimitives(long nativeServicePtr);
 
     static native long vibratorPerformEffect(
-            long controllerPtr, long effect, long strength, Vibration vibration);
+            long nativeServicePtr, long effect, long strength, long vibrationId);
 
-    static native void vibratorPerformComposedEffect(long controllerPtr,
-            VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
+    static native void vibratorPerformComposedEffect(long nativeServicePtr,
+            VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
 
-    static native void vibratorSetExternalControl(long controllerPtr, boolean enabled);
+    static native void vibratorSetExternalControl(long nativeServicePtr, boolean enabled);
 
-    static native long vibratorGetCapabilities(long controllerPtr);
-    static native void vibratorAlwaysOnEnable(long controllerPtr, long id, long effect,
+    static native long vibratorGetCapabilities(long nativeServicePtr);
+    static native void vibratorAlwaysOnEnable(long nativeServicePtr, long id, long effect,
             long strength);
-    static native void vibratorAlwaysOnDisable(long controllerPtr, long id);
+    static native void vibratorAlwaysOnDisable(long nativeServicePtr, long id);
 
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -220,12 +224,19 @@
         }
     };
 
+    /** Listener for vibration completion callbacks from native. */
+    public interface OnCompleteListener {
+
+        /** Callback triggered when vibration is complete, identified by {@link Vibration#id}. */
+        void onComplete(long vibrationId);
+    }
+
     /**
      * Holder for a vibration to be played. This class can be shared with native methods for
      * hardware callback support.
      */
-    @VisibleForTesting
-    public final class Vibration implements IBinder.DeathRecipient {
+    private final class Vibration implements IBinder.DeathRecipient {
+
         public final IBinder token;
         // Start time in CLOCK_BOOTTIME base.
         public final long startTime;
@@ -234,6 +245,7 @@
         // not to be affected by discontinuities created by RTC adjustments.
         public final long startTimeDebug;
         public final VibrationAttributes attrs;
+        public final long id;
         public final int uid;
         public final String opPkg;
         public final String reason;
@@ -248,6 +260,7 @@
                 VibrationAttributes attrs, int uid, String opPkg, String reason) {
             this.token = token;
             this.effect = effect;
+            this.id = mNextVibrationId.getAndIncrement();
             this.startTime = SystemClock.elapsedRealtime();
             this.startTimeDebug = System.currentTimeMillis();
             this.attrs = attrs;
@@ -268,19 +281,6 @@
             }
         }
 
-        /** Callback for when vibration is complete, to be called by native. */
-        @VisibleForTesting
-        public void onComplete() {
-            synchronized (mLock) {
-                if (this == mCurrentVibration) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Vibration finished by callback, cleaning up");
-                    }
-                    doCancelVibrateLocked();
-                }
-            }
-        }
-
         public boolean hasTimeoutLongerThan(long millis) {
             final long duration = effect.getDuration();
             return duration >= 0 && duration > millis;
@@ -385,14 +385,14 @@
         mNativeWrapper = injector.getNativeWrapper();
         mH = injector.createHandler(Looper.myLooper());
 
-        long controllerPtr = mNativeWrapper.vibratorInit();
+        long nativeServicePtr = mNativeWrapper.vibratorInit(this::onVibrationComplete);
         long finalizerPtr = mNativeWrapper.vibratorGetFinalizer();
 
         if (finalizerPtr != 0) {
             NativeAllocationRegistry registry =
                     NativeAllocationRegistry.createMalloced(
                             VibratorService.class.getClassLoader(), finalizerPtr);
-            registry.registerNativeAllocation(this, controllerPtr);
+            registry.registerNativeAllocation(this, nativeServicePtr);
         }
 
         // Reset the hardware to a default state, in case this is a runtime
@@ -549,6 +549,19 @@
         }
     }
 
+    /** Callback for when vibration is complete, to be called by native. */
+    @VisibleForTesting
+    public void onVibrationComplete(long vibrationId) {
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration finished by callback, cleaning up");
+                }
+                doCancelVibrateLocked();
+            }
+        }
+    }
+
     @Override // Binder call
     public boolean hasVibrator() {
         return doVibratorExists();
@@ -1266,18 +1279,18 @@
         return mNativeWrapper.vibratorExists();
     }
 
-    /** Vibrates with native callback trigger for {@link Vibration#onComplete()}. */
+    /** Vibrates with native callback trigger for {@link #onVibrationComplete(long)}. */
     private void doVibratorOn(long millis, int amplitude, Vibration vib) {
-        doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib);
+        doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib.id);
     }
 
     /** Vibrates without native callback. */
     private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
-        doVibratorOn(millis, amplitude, uid, attrs, /* vib= */ null);
+        doVibratorOn(millis, amplitude, uid, attrs, /* vibrationId= */ 0);
     }
 
     private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
-            @Nullable Vibration vib) {
+            long vibrationId) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
         try {
             synchronized (mInputDeviceVibrators) {
@@ -1299,7 +1312,7 @@
                     // 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, vib);
+                    mNativeWrapper.vibratorOn(millis, vibrationId);
                     doVibratorSetAmplitude(amplitude);
                 }
             }
@@ -1348,7 +1361,7 @@
             // Input devices don't support prebaked effect, so skip trying it with them.
             if (!usingInputDeviceVibrators) {
                 long duration = mNativeWrapper.vibratorPerformEffect(
-                        prebaked.getId(), prebaked.getEffectStrength(), vib);
+                        prebaked.getId(), prebaked.getEffectStrength(), vib.id);
                 if (duration > 0) {
                     noteVibratorOnLocked(vib.uid, duration);
                     return;
@@ -1395,7 +1408,7 @@
 
             PrimitiveEffect[] primitiveEffects =
                     composed.getPrimitiveEffects().toArray(new PrimitiveEffect[0]);
-            mNativeWrapper.vibratorPerformComposedEffect(primitiveEffects, vib);
+            mNativeWrapper.vibratorPerformComposedEffect(primitiveEffects, vib.id);
 
             // Composed effects don't actually give us an estimated duration, so we just guess here.
             noteVibratorOnLocked(vib.uid, 10 * primitiveEffects.length);
@@ -1726,20 +1739,20 @@
     @VisibleForTesting
     public static class NativeWrapper {
 
-        private long mNativeControllerPtr = 0;
+        private long mNativeServicePtr = 0;
 
         /** Checks if vibrator exists on device. */
         public boolean vibratorExists() {
-            return VibratorService.vibratorExists(mNativeControllerPtr);
+            return VibratorService.vibratorExists(mNativeServicePtr);
         }
 
         /**
          * Returns native pointer to newly created controller and initializes connection to vibrator
          * HAL service.
          */
-        public long vibratorInit() {
-            mNativeControllerPtr = VibratorService.vibratorInit();
-            return mNativeControllerPtr;
+        public long vibratorInit(OnCompleteListener listener) {
+            mNativeServicePtr = VibratorService.vibratorInit(listener);
+            return mNativeServicePtr;
         }
 
         /** Returns pointer to native finalizer function to be called by GC. */
@@ -1748,60 +1761,61 @@
         }
 
         /** Turns vibrator on for given time. */
-        public void vibratorOn(long milliseconds, @Nullable Vibration vibration) {
-            VibratorService.vibratorOn(mNativeControllerPtr, milliseconds, vibration);
+        public void vibratorOn(long milliseconds, long vibrationId) {
+            VibratorService.vibratorOn(mNativeServicePtr, milliseconds, vibrationId);
         }
 
         /** Turns vibrator off. */
         public void vibratorOff() {
-            VibratorService.vibratorOff(mNativeControllerPtr);
+            VibratorService.vibratorOff(mNativeServicePtr);
         }
 
         /** Sets the amplitude for the vibrator to run. */
         public void vibratorSetAmplitude(int amplitude) {
-            VibratorService.vibratorSetAmplitude(mNativeControllerPtr, amplitude);
+            VibratorService.vibratorSetAmplitude(mNativeServicePtr, amplitude);
         }
 
         /** Returns all predefined effects supported by the device vibrator. */
         public int[] vibratorGetSupportedEffects() {
-            return VibratorService.vibratorGetSupportedEffects(mNativeControllerPtr);
+            return VibratorService.vibratorGetSupportedEffects(mNativeServicePtr);
         }
 
         /** Returns all compose primitives supported by the device vibrator. */
         public int[] vibratorGetSupportedPrimitives() {
-            return VibratorService.vibratorGetSupportedPrimitives(mNativeControllerPtr);
+            return VibratorService.vibratorGetSupportedPrimitives(mNativeServicePtr);
         }
 
         /** Turns vibrator on to perform one of the supported effects. */
-        public long vibratorPerformEffect(long effect, long strength, Vibration vibration) {
+        public long vibratorPerformEffect(long effect, long strength, long vibrationId) {
             return VibratorService.vibratorPerformEffect(
-                    mNativeControllerPtr, effect, strength, vibration);
+                    mNativeServicePtr, effect, strength, vibrationId);
         }
 
         /** Turns vibrator on to perform one of the supported composed effects. */
         public void vibratorPerformComposedEffect(
-                VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration) {
-            VibratorService.vibratorPerformComposedEffect(mNativeControllerPtr, effect, vibration);
+                VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
+            VibratorService.vibratorPerformComposedEffect(mNativeServicePtr, effect,
+                    vibrationId);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
         public void vibratorSetExternalControl(boolean enabled) {
-            VibratorService.vibratorSetExternalControl(mNativeControllerPtr, enabled);
+            VibratorService.vibratorSetExternalControl(mNativeServicePtr, enabled);
         }
 
         /** Returns all capabilities of the device vibrator. */
         public long vibratorGetCapabilities() {
-            return VibratorService.vibratorGetCapabilities(mNativeControllerPtr);
+            return VibratorService.vibratorGetCapabilities(mNativeServicePtr);
         }
 
         /** Enable always-on vibration with given id and effect. */
         public void vibratorAlwaysOnEnable(long id, long effect, long strength) {
-            VibratorService.vibratorAlwaysOnEnable(mNativeControllerPtr, id, effect, strength);
+            VibratorService.vibratorAlwaysOnEnable(mNativeServicePtr, id, effect, strength);
         }
 
         /** Disable always-on vibration for given id. */
         public void vibratorAlwaysOnDisable(long id) {
-            VibratorService.vibratorAlwaysOnDisable(mNativeControllerPtr, id);
+            VibratorService.vibratorAlwaysOnDisable(mNativeServicePtr, id);
         }
     }
 
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 35e88eb..7d81d41 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2114,7 +2114,7 @@
                  * Owner or system user account was renamed, rename the account for
                  * those users with which the account was shared.
                  */
-                    List<UserInfo> users = getUserManager().getUsers(true);
+                    List<UserInfo> users = getUserManager().getAliveUsers();
                     for (UserInfo user : users) {
                         if (user.isRestricted()
                                 && (user.restrictedProfileParentId == parentUserId)) {
@@ -2373,7 +2373,7 @@
             int parentUserId = accounts.userId;
             if (canHaveProfile(parentUserId)) {
                 // Remove from any restricted profiles that are sharing this account.
-                List<UserInfo> users = getUserManager().getUsers(true);
+                List<UserInfo> users = getUserManager().getAliveUsers();
                 for (UserInfo user : users) {
                     if (user.isRestricted() && parentUserId == (user.restrictedProfileParentId)) {
                         removeSharedAccountAsUser(account, user.id, callingUid);
@@ -4267,7 +4267,7 @@
      */
     @NonNull
     public AccountAndUser[] getAllAccounts() {
-        final List<UserInfo> users = getUserManager().getUsers(true);
+        final List<UserInfo> users = getUserManager().getAliveUsers();
         final int[] userIds = new int[users.size()];
         for (int i = 0; i < userIds.length; i++) {
             userIds[i] = users.get(i).id;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6328cb60..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());
@@ -626,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));
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index e9539be..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.
     /**
@@ -281,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;
@@ -650,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);
 
@@ -866,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 3e600b7..2d803437 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -83,6 +83,7 @@
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
@@ -117,7 +118,6 @@
 import static com.android.server.am.MemoryStatUtil.hasMemcg;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
@@ -319,6 +319,7 @@
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -1370,6 +1371,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;
@@ -2246,6 +2251,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) {
@@ -4930,9 +4947,8 @@
                 notifyPackageUse(instr.mClass.getPackageName(),
                                  PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
             }
-            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
-                    + processName + " with config "
-                    + app.getWindowProcessController().getConfiguration());
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
+                    processName, app.getWindowProcessController().getConfiguration());
             ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info;
             app.compat = compatibilityInfoForPackage(appInfo);
 
@@ -13263,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
@@ -13276,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);
             }
@@ -17670,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);
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/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 0f2dfcc..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>(
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2e8660e..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,
@@ -4069,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/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 66677b67..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.
@@ -836,6 +837,9 @@
     }
 
     public void postNotification() {
+        if (hideFgNotification) {
+            return;
+        }
         final int appUid = appInfo.uid;
         final int appPid = app.pid;
         if (foregroundId != 0 && foregroundNoti != null) {
@@ -928,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/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 06ef58f..5447605 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -500,8 +500,8 @@
         sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
-    /*package*/ void postSetModeOwnerPid(int pid) {
-        sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
+    /*package*/ void postSetModeOwnerPid(int pid, int mode) {
+        sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -977,7 +977,9 @@
                         synchronized (mDeviceStateLock) {
                             if (mModeOwnerPid != msg.arg1) {
                                 mModeOwnerPid = msg.arg1;
-                                updateSpeakerphoneOn("setNewModeOwner");
+                                if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
+                                    updateSpeakerphoneOn("setNewModeOwner");
+                                }
                                 if (mModeOwnerPid != 0) {
                                     mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
                                 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bf7c68a..3f29eb5 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -285,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, ...)
@@ -1303,6 +1305,19 @@
     }
 
     /**
+     * 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*/);
+    }
+
+    /**
      * Update volume states for the given device.
      *
      * This will initialize the volume index if no volume index is available.
@@ -1311,10 +1326,14 @@
      * @param device a single audio device, ensure that this is not a devices bitmask
      * @param caller caller of this method
      */
-    private void updateVolumeStatesForAudioDevice(int device, String caller) {
+    private void onUpdateVolumeStatesForAudioDevice(int device, String caller) {
         final int numStreamTypes = AudioSystem.getNumStreamTypes();
-        for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            updateVolumeStates(device, streamType, caller);
+        synchronized (mSettingsLock) {
+            synchronized (VolumeStreamState.class) {
+                for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+                    updateVolumeStates(device, streamType, caller);
+                }
+            }
         }
     }
 
@@ -1346,7 +1365,7 @@
                     device)) {
                 mStreamStates[streamType].checkFixedVolumeDevices();
 
-                // Unmute streams if required if device is full volume
+                // Unmute streams if required and device is full volume
                 if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
                     mStreamStates[streamType].mute(false);
                 }
@@ -3728,13 +3747,15 @@
         private final IBinder mCb; // To be notified of client's death
         private final int mPid;
         private final int mUid;
-        private String mPackage;
+        private final boolean mIsPrivileged;
+        private final String mPackage;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
 
-        SetModeDeathHandler(IBinder cb, int pid, int uid, String caller) {
+        SetModeDeathHandler(IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
             mCb = cb;
             mPid = pid;
             mUid = uid;
+            mIsPrivileged = isPrivileged;
             mPackage = caller;
         }
 
@@ -3746,12 +3767,13 @@
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
                 } else {
-                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, mUid, TAG);
+                    newModeOwnerPid = setModeInt(
+                            AudioSystem.MODE_NORMAL, mCb, mPid, mUid, mIsPrivileged, TAG);
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
-            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, AudioService.this.getMode());
         }
 
         public int getPid() {
@@ -3777,6 +3799,10 @@
         public String getPackage() {
             return mPackage;
         }
+
+        public boolean isPrivileged() {
+            return mIsPrivileged;
+        }
     }
 
     /** @see AudioManager#setMode(int) */
@@ -3828,18 +3854,19 @@
                         + " without permission or being mode owner");
                 return;
             }
-            newModeOwnerPid = setModeInt(
-                mode, cb, callingPid, Binder.getCallingUid(), callingPackage);
+            newModeOwnerPid = setModeInt(mode, cb, callingPid, Binder.getCallingUid(),
+                    hasModifyPhoneStatePermission, callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
-        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, getMode());
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     @GuardedBy("mDeviceBroker.mSetModeLock")
-    private int setModeInt(int mode, IBinder cb, int pid, int uid, String caller) {
+    private int setModeInt(
+            int mode, IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
         if (DEBUG_MODE) {
             Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
                     + ", uid=" + uid + ", caller=" + caller + ")");
@@ -3891,7 +3918,7 @@
                 }
             } else {
                 if (hdlr == null) {
-                    hdlr = new SetModeDeathHandler(cb, pid, uid, caller);
+                    hdlr = new SetModeDeathHandler(cb, pid, uid, isPrivileged, caller);
                 }
                 // Register for client death notification
                 try {
@@ -3950,7 +3977,8 @@
             // change of mode may require volume to be re-applied on some devices
             updateAbsVolumeMultiModeDevices(oldMode, actualMode);
 
-            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION) {
+            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION
+                    && !hdlr.isPrivileged()) {
                 sendMsg(mAudioHandler,
                         MSG_CHECK_MODE_FOR_UID,
                         SENDMSG_QUEUE,
@@ -4948,18 +4976,20 @@
         }
     }
 
-    private void observeDevicesForStreams(int skipStream) {
-        synchronized (VolumeStreamState.class) {
-            for (int stream = 0; stream < mStreamStates.length; stream++) {
-                if (stream != skipStream) {
-                    int devices = 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#observeDevicesForStreams");
+                        Set<Integer> devicesSet = AudioSystem.generateAudioDeviceTypesSet(devices);
+                        for (Integer device : devicesSet) {
+                            // Update volume states for devices routed for the stream
+                            updateVolumeStates(device, stream,
+                                    "AudioService#onObserveDevicesForAllStreams");
+                        }
                     }
                 }
             }
@@ -4969,14 +4999,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*/);
     }
 
     /**
@@ -5029,7 +5061,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
-        updateVolumeStatesForAudioDevice(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller);
+        postUpdateVolumeStatesForAudioDevice(audioSystemDeviceOut,
+                "setDeviceVolumeBehavior:" + caller);
     }
 
     /**
@@ -5660,6 +5693,7 @@
             }
         }
 
+        @GuardedBy("VolumeStreamState.class")
         public int observeDevicesForStream_syncVSS(boolean checkOthers) {
             if (!mSystemServer.isPrivileged()) {
                 return AudioSystem.DEVICE_NONE;
@@ -5672,15 +5706,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;
         }
 
@@ -6452,7 +6490,7 @@
                     break;
 
                 case MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS:
-                    onObserveDevicesForAllStreams();
+                    onObserveDevicesForAllStreams(/*skipStream*/ msg.arg1);
                     break;
 
                 case MSG_HDMI_VOLUME_CHECK:
@@ -6490,11 +6528,21 @@
                                     CHECK_MODE_FOR_UID_PERIOD_MS);
                             break;
                         }
-                        // For now just log the fact that an app is hogging the audio mode.
-                        // TODO(b/160260850): remove abusive app from audio mode stack.
+                        setModeInt(AudioSystem.MODE_NORMAL, h.getBinder(), h.getPid(), h.getUid(),
+                                h.isPrivileged(), "MSG_CHECK_MODE_FOR_UID");
                         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;
             }
         }
     }
@@ -7251,7 +7299,7 @@
                 // HDMI output
                 removeAudioSystemDeviceOutFromFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI);
             }
-            updateVolumeStatesForAudioDevice(AudioSystem.DEVICE_OUT_HDMI,
+            postUpdateVolumeStatesForAudioDevice(AudioSystem.DEVICE_OUT_HDMI,
                     "HdmiPlaybackClient.DisplayStatusCallback");
         }
     }
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 e87c8fe..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) {
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 47c839c..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;
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 fffc073..588e865 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -175,7 +175,7 @@
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
-    @NonNull protected final InternalCallback mInternalCallback;
+    @NonNull private final InternalCallback mInternalCallback;
     @NonNull private final Queue<Operation> mPendingOperations;
     @Nullable private Operation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
@@ -185,7 +185,15 @@
     // starting the next client).
     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
@@ -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(mInternalCallback);
+            interruptable.cancelWithoutStarting(getInternalCallback());
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
@@ -280,10 +296,7 @@
         final boolean shouldStartNow = currentClient.getCookie() == 0;
         if (shouldStartNow) {
             Slog.d(getTag(), "[Starting] " + mCurrentOperation);
-            if (mCurrentOperation.mClientCallback != null) {
-                mCurrentOperation.mClientCallback.onClientStarted(currentClient);
-            }
-            currentClient.start(mInternalCallback);
+            currentClient.start(getInternalCallback());
             mCurrentOperation.state = Operation.STATE_STARTED;
         } else {
             try {
@@ -326,11 +339,8 @@
         }
 
         Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
-        if (mCurrentOperation.mClientCallback != null) {
-            mCurrentOperation.mClientCallback.onClientStarted(mCurrentOperation.clientMonitor);
-        }
         mCurrentOperation.state = Operation.STATE_STARTED;
-        mCurrentOperation.clientMonitor.start(mInternalCallback);
+        mCurrentOperation.clientMonitor.start(getInternalCallback());
     }
 
     /**
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 81867b0..dec40e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -144,6 +144,7 @@
      */
     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 2c60d09..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);
         }
     }
 
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 2c5b802..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,7 +40,7 @@
     @Override
     public void unableToStart() {
         try {
-            getListener().onChallengeGenerated(0L);
+            getListener().onChallengeGenerated(getSensorId(), 0L);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send error", e);
         }
@@ -52,7 +52,7 @@
 
         startHalOperation();
         try {
-            getListener().onChallengeGenerated(mChallenge);
+            getListener().onChallengeGenerated(getSensorId(), mChallenge);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
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/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index a4956cb..d9c62df 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
@@ -44,6 +44,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;
@@ -274,7 +275,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 */);
@@ -378,7 +382,7 @@
         //    is safe because authenticatorIds only change when A) new template has been enrolled,
         //    or B) all templates are removed.
         mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
                     scheduleUpdateActiveUserWithoutHandler(targetUserId);
@@ -464,6 +468,22 @@
         });
     }
 
+    /**
+     * {@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
+     *android.hardware.face.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(() -> {
@@ -498,7 +518,8 @@
     void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
         mHandler.post(() -> {
             if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
-                Slog.e(TAG, "Package: " + owner + " attempting to revoke challenge owned by: "
+                Slog.e(TAG, "scheduleRevokeChallenge, package: " + owner
+                        + " attempting to revoke challenge owned by: "
                         + mCurrentChallengeOwner.getOwnerString());
                 return;
             }
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/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/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index 20a1807..3754bd7 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);
     }
@@ -387,7 +440,7 @@
         //    is safe because authenticatorIds only change when A) new template has been enrolled,
         //    or B) all templates are removed.
         mHandler.post(() -> {
-            for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 final int targetUserId = user.id;
                 if (!mAuthenticatorIds.containsKey(targetUserId)) {
                     scheduleUpdateActiveUserWithoutHandler(targetUserId);
@@ -419,7 +472,7 @@
         });
     }
 
-    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(() -> {
@@ -492,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);
 
@@ -500,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 751df8e..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
@@ -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/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/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/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 7f9b3c9..4c63eb4 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;
@@ -56,9 +47,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 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;
@@ -72,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.
@@ -120,51 +114,9 @@
         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);
-        }
     }
 
-    /**
-     * A data class to store each uid Netd permission information. Netd permissions includes
-     * PERMISSION_NETWORK, PERMISSION_SYSTEM, PERMISSION_INTERNET, PERMISSION_UPDATE_DEVICE_STATS
-     * and OR'd with the others. Default permission is PERMISSION_NONE and PERMISSION_UNINSTALLED
-     * will be set if all packages are removed from the uid.
-     */
-    public static class UidNetdPermissionInfo {
-        private final int mNetdPermissions;
-
-        UidNetdPermissionInfo() {
-            this(PERMISSION_NONE);
-        }
-
-        UidNetdPermissionInfo(int permissions) {
-            mNetdPermissions = permissions;
-        }
-
-        /** Plus given permissions and return new UidNetdPermissionInfo instance. */
-        public UidNetdPermissionInfo plusNetdPermissions(int permissions) {
-            return new UidNetdPermissionInfo(mNetdPermissions | permissions);
-        }
-
-        /** Return whether package is uninstalled. */
-        public boolean isPackageUninstalled() {
-            return mNetdPermissions == PERMISSION_UNINSTALLED;
-        }
-
-        /** Check that uid has given permissions */
-        public boolean hasNetdPermissions(final int permissions) {
-            if (isPackageUninstalled()) return false;
-            if (permissions == PERMISSION_NONE) return true;
-            return (mNetdPermissions & permissions) == permissions;
-        }
-    }
-
-    public PermissionMonitor(Context context, INetd netd) {
+    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
         this(context, netd, new Dependencies());
     }
 
@@ -195,7 +147,7 @@
             return;
         }
 
-        final SparseArray<UidNetdPermissionInfo> netdPermsUids = new SparseArray<>();
+        SparseIntArray netdPermsUids = new SparseIntArray();
 
         for (PackageInfo app : apps) {
             int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
@@ -204,9 +156,8 @@
             }
             mAllApps.add(UserHandle.getAppId(uid));
 
-            final boolean isNetwork = hasPermission(CHANGE_NETWORK_STATE, uid);
-            final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(uid)
-                    || isCarryoverPackage(app.applicationInfo);
+            boolean isNetwork = hasNetworkPermission(app);
+            boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
 
             if (isNetwork || hasRestrictedPermission) {
                 Boolean permission = mApps.get(uid);
@@ -217,16 +168,13 @@
                 }
             }
 
-            // Skip already checked uid.
-            if (netdPermsUids.get(uid) != null) continue;
-
             //TODO: unify the management of the permissions into one codepath.
-            final UidNetdPermissionInfo permInfo =
-                    new UidNetdPermissionInfo(getNetdPermissionMask(uid));
-            netdPermsUids.put(uid, permInfo);
+            int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
+                    app.requestedPermissionsFlags);
+            netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
         }
 
-        List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
+        List<UserInfo> users = mUserManager.getAliveUsers();
         if (users != null) {
             for (UserInfo user : users) {
                 mUsers.add(user.id);
@@ -238,17 +186,15 @@
         for (int i = 0; i < systemPermission.size(); i++) {
             ArraySet<String> perms = systemPermission.valueAt(i);
             int uid = systemPermission.keyAt(i);
-            int netdPermission = PERMISSION_NONE;
+            int netdPermission = 0;
             // 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;
             }
-            final UidNetdPermissionInfo permInfo = netdPermsUids.get(uid);
-            netdPermsUids.put(uid, permInfo != null
-                    ? permInfo.plusNetdPermissions(netdPermission)
-                    : new UidNetdPermissionInfo(netdPermission));
+            netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
         }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
         update(mUsers, mApps, true);
@@ -261,34 +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
-    // TODO : remove this check in the future(b/162295056). All apps should just request the
-    // appropriate permission for their use case since android Q.
-    boolean isCarryoverPackage(@Nullable final ApplicationInfo appInfo) {
-        if (appInfo == null) return false;
-        return (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);
+    boolean hasNetworkPermission(@NonNull final PackageInfo app) {
+        return hasPermission(app, CHANGE_NETWORK_STATE);
     }
 
     @VisibleForTesting
-    boolean hasRestrictedNetworkPermission(final int uid) {
-        return hasPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, uid)
-                || hasPermission(PERMISSION_MAINLINE_NETWORK_STACK, uid)
-                || hasPermission(NETWORK_STACK, uid);
+    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 (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(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);
     }
@@ -313,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);
@@ -363,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(uid)
-                    || isCarryoverPackage(app.applicationInfo);
+            final boolean isNetwork = hasNetworkPermission(app);
+            final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
             if (isNetwork || hasRestrictedPermission) {
                 currentPermission = hasRestrictedPermission;
             }
@@ -382,15 +341,24 @@
         return currentPermission;
     }
 
-    private UidNetdPermissionInfo getPermissionForUid(final int uid) {
+    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 new UidNetdPermissionInfo(PERMISSION_UNINSTALLED);
+            permission = INetd.PERMISSION_UNINSTALLED;
         }
-        return new UidNetdPermissionInfo(getNetdPermissionMask(uid));
+        return permission;
     }
 
     /**
@@ -407,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);
 
@@ -463,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.
@@ -499,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;
     }
@@ -640,28 +614,28 @@
      * permission information to netd.
      *
      * @param uid the app uid of the package installed
-     * @param permissionInfo the permission info of given uid.
+     * @param permissions the permissions the app requested and netd cares about.
      *
      * @hide
      */
     @VisibleForTesting
-    void sendPackagePermissionsForUid(int uid, UidNetdPermissionInfo permissionInfo) {
-        final SparseArray<UidNetdPermissionInfo> uidsPermInfo = new SparseArray<>();
-        uidsPermInfo.put(uid, permissionInfo);
-        sendPackagePermissionsToNetd(uidsPermInfo);
+    void sendPackagePermissionsForUid(int uid, int permissions) {
+        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+        netdPermissionsAppIds.put(uid, permissions);
+        sendPackagePermissionsToNetd(netdPermissionsAppIds);
     }
 
     /**
      * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
      * and/or UPDATE_DEVICE_STATS permission of the uids in array.
      *
-     * @param uidsPermInfo permission info array generated from each uid. If the uid permission is
-     *                     PERMISSION_NONE or PERMISSION_UNINSTALLED, revoke all permissions of that
-     *                     uid.
+     * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
+     * permission is 0, revoke all permissions of that uid.
+     *
      * @hide
      */
     @VisibleForTesting
-    void sendPackagePermissionsToNetd(final SparseArray<UidNetdPermissionInfo> uidsPermInfo) {
+    void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
         if (mNetd == null) {
             Log.e(TAG, "Failed to get the netd service");
             return;
@@ -671,44 +645,50 @@
         ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
-        for (int i = 0; i < uidsPermInfo.size(); i++) {
-            final int uid = uidsPermInfo.keyAt(i);
-            final UidNetdPermissionInfo permInfo = uidsPermInfo.valueAt(i);
-            if (permInfo.hasNetdPermissions(
-                    PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS)) {
-                allPermissionAppIds.add(uid);
-            } else if (permInfo.hasNetdPermissions(PERMISSION_INTERNET)) {
-                internetPermissionAppIds.add(uid);
-            } else if (permInfo.hasNetdPermissions(PERMISSION_UPDATE_DEVICE_STATS)) {
-                updateStatsPermissionAppIds.add(uid);
-            } else if (permInfo.isPackageUninstalled()) {
-                uninstalledAppIds.add(uid);
-            } else {
-                noPermissionAppIds.add(uid);
+        for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
+            int permissions = netdPermissionsAppIds.valueAt(i);
+            switch(permissions) {
+                case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
+                    allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_INTERNET:
+                    internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_UPDATE_DEVICE_STATS:
+                    updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_NONE:
+                    noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_UNINSTALLED:
+                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+                default:
+                    Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
+                            + netdPermissionsAppIds.keyAt(i));
             }
         }
         try {
             // 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 5484bfc..7175489 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1463,7 +1463,7 @@
             final long token = Binder.clearCallingIdentity();
             List<UserInfo> users;
             try {
-                users = UserManager.get(mContext).getUsers(true);
+                users = UserManager.get(mContext).getAliveUsers();
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 041bedc..b33aa0a 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.
@@ -366,7 +367,7 @@
     }
 
     private void removeStaleAccounts() {
-        for (UserInfo user : mUserManager.getUsers(true)) {
+        for (UserInfo user : mUserManager.getAliveUsers()) {
             // Skip any partially created/removed users
             if (user.partial) continue;
             Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
@@ -776,7 +777,7 @@
         if (!mSyncStorageEngine.shouldGrantSyncAdaptersAccountAccess()) {
             return;
         }
-        List<UserInfo> users = mUserManager.getUsers(true);
+        List<UserInfo> users = mUserManager.getAliveUsers();
         final int userCount = users.size();
         for (int i = 0; i < userCount; i++) {
             UserHandle userHandle = users.get(i).getUserHandle();
@@ -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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index dab8c7f..2c632d9 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2174,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;
             }
 
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/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 3f949ba..653323d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -25,7 +25,7 @@
  * A helper class to build {@link HdmiCecMessage} from various cec commands.
  */
 public class HdmiCecMessageBuilder {
-    private static final int OSD_NAME_MAX_LENGTH = 13;
+    private static final int OSD_NAME_MAX_LENGTH = 14;
 
     private HdmiCecMessageBuilder() {}
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9ab410d..3fddd5a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2591,7 +2591,7 @@
             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                 mCurMethod = IInputMethod.Stub.asInterface(service);
                 final String curMethodPackage = mCurIntent.getComponent().getPackageName();
-                final int curMethodUid = mPackageManagerInternal.getPackageUidInternal(
+                final int curMethodUid = mPackageManagerInternal.getPackageUid(
                         curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
                 if (curMethodUid < 0) {
                     Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 71f1833..534533f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1133,9 +1133,7 @@
             return;
         }
 
-        String dumpFilter = args.length == 0 ? null : args[0];
-
-        ipw.println("Location Manager State:");
+        ipw.print("Location Manager State:");
         ipw.increaseIndent();
         ipw.println("Elapsed Realtime: " + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
 
@@ -1166,34 +1164,28 @@
                 ipw.println(
                         "Location Controller Extra Package: " + mExtraLocationControllerPackage
                                 + (mExtraLocationControllerPackageEnabled ? " [enabled]"
-                                : "[disabled]"));
+                                : " [disabled]"));
             }
         }
 
         ipw.println("Location Providers:");
         ipw.increaseIndent();
         for (LocationProviderManager manager : mProviderManagers) {
-            if (dumpFilter == null || manager.getName().equals(dumpFilter)) {
-                manager.dump(fd, ipw, args);
-            }
+            manager.dump(fd, ipw, args);
         }
         ipw.decreaseIndent();
 
-        if (dumpFilter == null || GPS_PROVIDER.equals(dumpFilter)) {
-            if (mGnssManagerService != null) {
-                ipw.println("GNSS Manager:");
-                ipw.increaseIndent();
-                mGnssManagerService.dump(fd, ipw, args);
-                ipw.decreaseIndent();
-            }
-        }
-
-        if (dumpFilter == null || "geofence".equals(dumpFilter)) {
-            ipw.println("Geofence Manager:");
+        if (mGnssManagerService != null) {
+            ipw.println("GNSS Manager:");
             ipw.increaseIndent();
-            mGeofenceManager.dump(fd, ipw, args);
+            mGnssManagerService.dump(fd, ipw, args);
             ipw.decreaseIndent();
         }
+
+        ipw.println("Geofence Manager:");
+        ipw.increaseIndent();
+        mGeofenceManager.dump(fd, ipw, args);
+        ipw.decreaseIndent();
     }
 
     private class LocalService extends LocationManagerInternal {
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 05aa315..66245a2 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -84,7 +84,7 @@
 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.listeners.RemoteListenerRegistration;
 import com.android.server.location.util.AppForegroundHelper;
 import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
 import com.android.server.location.util.AppOpsHelper;
@@ -154,15 +154,8 @@
 
         @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();
-                        }
-                    });
+                @Nullable Runnable onCompleteCallback) throws RemoteException {
+            mListener.onLocationChanged(location, SingleUseCallback.wrap(onCompleteCallback));
         }
 
         @Override
@@ -221,7 +214,7 @@
     }
 
     protected abstract class Registration extends
-            RemovableListenerRegistration<LocationRequest, LocationTransport> {
+            RemoteListenerRegistration<LocationRequest, LocationTransport> {
 
         @PermissionLevel protected final int mPermissionLevel;
         private final WorkSource mWorkSource;
@@ -306,11 +299,20 @@
         }
 
         @Override
-        protected final void onInactive() {
+        protected final ListenerOperation<LocationTransport> onInactive() {
             onHighPowerUsageChanged();
             if (!getRequest().getHideFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
             }
+            return null;
+        }
+
+        @Override
+        public final <Listener> void onOperationFailure(ListenerOperation<Listener> operation,
+                Exception e) {
+            synchronized (mLock) {
+                super.onOperationFailure(operation, e);
+            }
         }
 
         @Override
@@ -818,6 +820,12 @@
         @GuardedBy("mLock")
         @Override
         protected void onProviderListenerRegister() {
+            try {
+                ((IBinder) getKey()).linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                remove();
+            }
+
             mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
                     SystemClock.elapsedRealtime());
 
@@ -829,12 +837,6 @@
                         0, this, FgThread.getHandler(), getWorkSource());
             }
 
-            try {
-                ((IBinder) getKey()).linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                remove();
-            }
-
             // start listening for provider enabled/disabled events
             addEnabledListener(this);
 
@@ -1058,8 +1060,13 @@
             mUserInfoHelper.addListener(mUserChangedListener);
             mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
 
-            // initialize enabled state
-            onUserStarted(UserHandle.USER_ALL);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                // initialize enabled state
+                onUserStarted(UserHandle.USER_ALL);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -1069,10 +1076,15 @@
             mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
 
             // notify and remove all listeners
-            onUserStopped(UserHandle.USER_ALL);
-            removeRegistrationIf(key -> true);
-            mEnabledListeners.clear();
+            long identity = Binder.clearCallingIdentity();
+            try {
+                onUserStopped(UserHandle.USER_ALL);
+                removeRegistrationIf(key -> true);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
 
+            mEnabledListeners.clear();
             mStarted = false;
         }
     }
@@ -1133,14 +1145,26 @@
     public void setRealProvider(AbstractLocationProvider provider) {
         synchronized (mLock) {
             Preconditions.checkState(mStarted);
-            mProvider.setRealProvider(provider);
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mProvider.setRealProvider(provider);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     public void setMockProvider(@Nullable MockProvider provider) {
         synchronized (mLock) {
             Preconditions.checkState(mStarted);
-            mProvider.setMockProvider(provider);
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mProvider.setMockProvider(provider);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
 
             // 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
@@ -1162,7 +1186,12 @@
                 throw new IllegalArgumentException(mName + " provider is not a test provider");
             }
 
-            mProvider.setMockProviderAllowed(enabled);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mProvider.setMockProviderAllowed(enabled);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -1172,15 +1201,20 @@
                 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);
-            }
+            long identity = Binder.clearCallingIdentity();
+            try {
+                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);
+                mProvider.setMockProviderLocation(location);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -1271,7 +1305,7 @@
         }
     }
 
-    public void getCurrentLocation(LocationRequest request, CallerIdentity identity,
+    public void getCurrentLocation(LocationRequest request, CallerIdentity callerIdentity,
             int permissionLevel, ICancellationSignal cancellationTransport,
             ILocationCallback callback) {
         Preconditions.checkArgument(mName.equals(request.getProvider()));
@@ -1283,12 +1317,12 @@
         GetCurrentLocationListenerRegistration registration =
                 new GetCurrentLocationListenerRegistration(
                         request,
-                        identity,
+                        callerIdentity,
                         new GetCurrentLocationTransport(callback),
                         permissionLevel);
 
         synchronized (mLock) {
-            Location lastLocation = getLastLocation(request, identity, permissionLevel);
+            Location lastLocation = getLastLocation(request, callerIdentity, permissionLevel);
             if (lastLocation != null) {
                 long locationAgeMs = NANOSECONDS.toMillis(
                         SystemClock.elapsedRealtimeNanos()
@@ -1306,7 +1340,13 @@
             }
 
             // if last location isn't good enough then we add a location request
-            addRegistration(callback.asBinder(), registration);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                addRegistration(callback.asBinder(), registration);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+
             CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                     cancellationTransport);
             if (cancellationSignal != null) {
@@ -1321,48 +1361,73 @@
     }
 
     public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
-        mProvider.sendExtraCommand(uid, pid, command, extras);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mProvider.sendExtraCommand(uid, pid, command, extras);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
-    public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+    public void registerLocationRequest(LocationRequest request, CallerIdentity callerIdentity,
             @PermissionLevel int permissionLevel, ILocationListener listener) {
         Preconditions.checkArgument(mName.equals(request.getProvider()));
 
         synchronized (mLock) {
-            addRegistration(
-                    listener.asBinder(),
-                    new LocationListenerRegistration(
-                            request,
-                            identity,
-                            new LocationListenerTransport(listener),
-                            permissionLevel));
+            long identity = Binder.clearCallingIdentity();
+            try {
+                addRegistration(
+                        listener.asBinder(),
+                        new LocationListenerRegistration(
+                                request,
+                                callerIdentity,
+                                new LocationListenerTransport(listener),
+                                permissionLevel));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
-    public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+    public void registerLocationRequest(LocationRequest request, CallerIdentity callerIdentity,
             @PermissionLevel int permissionLevel, PendingIntent pendingIntent) {
         Preconditions.checkArgument(mName.equals(request.getProvider()));
 
         synchronized (mLock) {
-            addRegistration(
-                    pendingIntent,
-                    new LocationPendingIntentRegistration(
-                            request,
-                            identity,
-                            new LocationPendingIntentTransport(mContext, pendingIntent),
-                            permissionLevel));
+            long identity = Binder.clearCallingIdentity();
+            try {
+                addRegistration(
+                        pendingIntent,
+                        new LocationPendingIntentRegistration(
+                                request,
+                                callerIdentity,
+                                new LocationPendingIntentTransport(mContext, pendingIntent),
+                                permissionLevel));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     public void unregisterLocationRequest(ILocationListener listener) {
         synchronized (mLock) {
-            removeRegistration(listener.asBinder());
+            long identity = Binder.clearCallingIdentity();
+            try {
+                removeRegistration(listener.asBinder());
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     public void unregisterLocationRequest(PendingIntent pendingIntent) {
         synchronized (mLock) {
-            removeRegistration(pendingIntent);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                removeRegistration(pendingIntent);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -1950,4 +2015,50 @@
             }
         }
     }
+
+    private static class SingleUseCallback extends IRemoteCallback.Stub {
+
+        @Nullable
+        public static IRemoteCallback wrap(@Nullable Runnable callback) {
+            return callback == null ? null : new SingleUseCallback(callback);
+        }
+
+        @GuardedBy("this")
+        @Nullable private Runnable mCallback;
+
+        private SingleUseCallback(Runnable callback) {
+            mCallback = Objects.requireNonNull(callback);
+        }
+
+        @Override
+        public void sendResult(Bundle data) {
+            Runnable callback;
+            synchronized (this) {
+                callback = mCallback;
+                mCallback = null;
+            }
+
+            // prevent this callback from being run more than once - otherwise this could provide an
+            // attack vector for a malicious app to break assumptions on how many times a callback
+            // may be invoked, and thus crash system server.
+            if (callback == null) {
+                return;
+            }
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+                callback.run();
+            } catch (RuntimeException e) {
+                // since this is within a oneway binder transaction there is nowhere
+                // for exceptions to go - move onto another thread to crash system
+                // server so we find out about it
+                FgThread.getExecutor().execute(() -> {
+                    throw new AssertionError(e);
+                });
+                throw e;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 2d9734e..2d7f028 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -32,6 +32,7 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.WorkSource;
@@ -291,17 +292,28 @@
             @Nullable String attributionTag) {
         LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_FINE);
 
-        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
-                AppOpsManager.toReceiverId(pendingIntent));
-        addRegistration(new GeofenceKey(pendingIntent, geofence),
-                new GeofenceRegistration(geofence, identity, pendingIntent));
+        CallerIdentity callerIdentity = CallerIdentity.fromBinder(mContext, packageName,
+                attributionTag, AppOpsManager.toReceiverId(pendingIntent));
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            addRegistration(new GeofenceKey(pendingIntent, geofence),
+                    new GeofenceRegistration(geofence, callerIdentity, pendingIntent));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Removes the geofence associated with the PendingIntent.
      */
     public void removeGeofence(PendingIntent pendingIntent) {
-        removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
+        long identity = Binder.clearCallingIdentity();
+        try {
+            removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
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 1b599b0..a9fdacc 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -25,6 +25,7 @@
 import android.location.LocationManagerInternal;
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Process;
@@ -218,16 +219,27 @@
     /**
      * Adds a listener with the given identity and request.
      */
-    protected void addListener(TRequest request, CallerIdentity identity, TListener listener) {
-        addRegistration(listener.asBinder(),
-                new GnssListenerRegistration(request, identity, listener));
+    protected void addListener(TRequest request, CallerIdentity callerIdentity,
+            TListener listener) {
+        long identity = Binder.clearCallingIdentity();
+        try {
+            addRegistration(listener.asBinder(),
+                    new GnssListenerRegistration(request, callerIdentity, listener));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Removes the given listener.
      */
     public void removeListener(TListener listener) {
-        removeRegistration(listener.asBinder());
+        long identity = Binder.clearCallingIdentity();
+        try {
+            removeRegistration(listener.asBinder());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
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 850cf7f..5c30fe8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -2022,6 +2022,21 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        boolean dumpAll = false;
+
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                dumpAll = true;
+                break;
+            }
+        }
+
         StringBuilder s = new StringBuilder();
         s.append("mStarted=").append(mStarted).append("   (changed ");
         TimeUtils.formatDuration(SystemClock.elapsedRealtime()
@@ -2053,9 +2068,11 @@
             s.append("]\n");
         }
         s.append(mGnssMetrics.dumpGnssMetricsAsText());
-        s.append("native internal state: \n");
-        s.append("  ").append(native_get_internal_state());
-        s.append("\n");
+        if (dumpAll) {
+            s.append("native internal state: \n");
+            s.append("  ").append(native_get_internal_state());
+            s.append("\n");
+        }
         pw.append(s);
     }
 
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index bd8bce8..58aabda 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -32,10 +32,10 @@
  * @param <TListener> listener type
  */
 public abstract class BinderListenerRegistration<TRequest, TListener> extends
-        RemovableListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+        RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
 
     /**
-     * Interface to allowed binder retrieval when keys are not themselves IBinder.
+     * Interface to allow binder retrieval when keys are not themselves IBinders.
      */
     public interface BinderKey {
         /**
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 f94de9b..8a6b8aa 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -18,12 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Binder;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
@@ -31,8 +28,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -42,7 +41,7 @@
  * divided into two categories, active registrations and inactive registrations, as defined by
  * {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
  * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
- * whose active state may have changed.
+ * whose active state may have changed. Listeners will only be invoked for active registrations.
  *
  * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
  *
@@ -64,14 +63,6 @@
  * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
  * ensures re-entrant removal does not accidentally remove the incorrect registration.
  *
- * All callbacks will be invoked with a cleared binder identity.
- *
- * Listeners owned by other processes will be run on a direct executor (and thus while holding a
- * lock). Listeners owned by the same process this multiplexer is in will be run asynchronously (and
- * thus without holding a lock). The underlying assumption is that listeners owned by other
- * processes will simply be forwarding the call to those other processes and perhaps performing
- * simple bookkeeping, with no potential for deadlock.
- *
  * @param <TKey>           key type
  * @param <TRequest>       request type
  * @param <TListener>      listener type
@@ -149,46 +140,51 @@
     }
 
     /**
-     * Invoked before the first registration occurs. This is a convenient entry point for
-     * registering listeners, etc, which only need to be present while there are any registrations.
+     * Invoked when the multiplexer goes from having no registrations to having some registrations.
+     * This is a convenient entry point for registering listeners, etc, which only need to be
+     * present while there are any registrations. Invoked while holding the multiplexer's internal
+     * lock.
      */
     protected void onRegister() {}
 
     /**
-     * Invoked after the last unregistration occurs. This is a convenient entry point for
-     * unregistering listeners, etc, which only need to be present while there are any
-     * registrations.
+     * Invoked when the multiplexer goes from having some registrations to having no registrations.
+     * This is a convenient entry point for unregistering listeners, etc, which only need to be
+     * present while there are any registrations. Invoked while holding the multiplexer's internal
+     * lock.
      */
     protected void onUnregister() {}
 
     /**
-     * Invoked when a registration is added.
+     * Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
      */
     protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
 
     /**
-     * Invoked when a registration is removed.
+     * Invoked when a registration is removed. Invoked while holding the multiplexer's internal
+     * lock.
      */
     protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
 
     /**
-     * Invoked when the manager goes from having no active registrations to having some active
+     * Invoked when the multiplexer goes from having no active registrations to having some active
      * registrations. This is a convenient entry point for registering listeners, etc, which only
-     * need to be present while there are active registrations.
+     * need to be present while there are active registrations. Invoked while holding the
+     * multiplexer's internal lock.
      */
     protected void onActive() {}
 
     /**
-     * Invoked when the manager goes from having some active registrations to having no active
+     * Invoked when the multiplexer goes from having some active registrations to having no active
      * registrations. This is a convenient entry point for unregistering listeners, etc, which only
-     * need to be present while there are active registrations.
+     * need to be present while there are active registrations. Invoked while holding the
+     * multiplexer's internal lock.
      */
     protected void onInactive() {}
 
     /**
-     * Adds a new registration with the given key. Registration may fail if
-     * {@link ListenerRegistration#onRegister(Object)} returns false, in which case the registration
-     * will not be added. This method cannot be called to add a registration re-entrantly.
+     * Adds a new registration with the given key. This method cannot be called to add a
+     * registration re-entrantly.
      */
     protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
         Objects.requireNonNull(key);
@@ -204,7 +200,6 @@
             // involve removing a prior registration. note that try-with-resources ordering is
             // meaningful here as well. we want to close the reentrancy guard first, as this may
             // generate additional service updates, then close the update service buffer.
-            long identity = Binder.clearCallingIdentity();
             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
 
@@ -224,16 +219,13 @@
                 registration.onRegister(key);
                 onRegistrationAdded(key, registration);
                 onRegistrationActiveChanged(registration);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
         }
     }
 
     /**
-     * Removes the registration with the given key. If unregistration occurs,
-     * {@link #onRegistrationRemoved(Object, ListenerRegistration)} will be called. This method
-     * cannot be called to remove a registration re-entrantly.
+     * Removes the registration with the given key. This method cannot be called to remove a
+     * registration re-entrantly.
      */
     protected final void removeRegistration(@NonNull Object key) {
         synchronized (mRegistrations) {
@@ -250,9 +242,8 @@
     }
 
     /**
-     * Removes all registrations with keys that satisfy the given predicate. If unregistration
-     * occurs, {@link #onRegistrationRemoved(Object, ListenerRegistration)} will be called. This
-     * method cannot be called to remove a registration re-entrantly.
+     * Removes all registrations with keys that satisfy the given predicate. This method cannot be
+     * called to remove a registration re-entrantly.
      */
     protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
         synchronized (mRegistrations) {
@@ -281,11 +272,8 @@
 
     /**
      * Removes the given registration with the given key. If the given key has a different
-     * registration at the time this method is called, nothing happens. If unregistration occurs,
-     * {@link #onRegistrationRemoved(Object, ListenerRegistration)} will be called. This method
-     * allows for re-entrancy, and may be called to remove a registration re-entrantly. In this case
-     * the registration will immediately be marked inactive and unregistered, and will be removed
-     * completely at some later time.
+     * registration at the time this method is called, nothing happens. This method allows for
+     * re-entrancy, and may be called to remove a registration re-entrantly.
      */
     protected final void removeRegistration(@NonNull Object key,
             @NonNull ListenerRegistration<?, ?> registration) {
@@ -324,7 +312,6 @@
         // in multiple service updates. note that try-with-resources ordering is meaningful here as
         // well. we want to close the reentrancy guard first, as this may generate additional
         // service updates, then close the update service buffer.
-        long identity = Binder.clearCallingIdentity();
         try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
              ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
 
@@ -337,8 +324,6 @@
                     onUnregister();
                 }
             }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
         }
     }
 
@@ -362,34 +347,42 @@
                 }
             }
 
-            long identity = Binder.clearCallingIdentity();
-            try {
-                if (actives.isEmpty()) {
+            if (actives.isEmpty()) {
+                mCurrentRequest = null;
+                if (mServiceRegistered) {
+                    mServiceRegistered = false;
                     mCurrentRequest = null;
-                    if (mServiceRegistered) {
-                        mServiceRegistered = false;
-                        mCurrentRequest = null;
-                        unregisterWithService();
-                    }
-                    return;
+                    unregisterWithService();
                 }
-
-                TMergedRequest merged = mergeRequests(actives);
-                if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
-                    if (mServiceRegistered) {
-                        mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
-                    } else {
-                        mServiceRegistered = registerWithService(merged);
-                    }
-                    if (mServiceRegistered) {
-                        mCurrentRequest = merged;
-                    } else {
-                        mCurrentRequest = null;
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                return;
             }
+
+            TMergedRequest merged = mergeRequests(actives);
+            if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
+                if (mServiceRegistered) {
+                    mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
+                } else {
+                    mServiceRegistered = registerWithService(merged);
+                }
+                if (mServiceRegistered) {
+                    mCurrentRequest = merged;
+                } else {
+                    mCurrentRequest = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Clears currently stored service state, and invokes {@link #updateService()} to force a new
+     * call to {@link #registerWithService(Object)} if necessary. This is useful, for instance, if
+     * the backing service has crashed or otherwise lost state, and needs to be re-initialized.
+     */
+    protected final void resetService() {
+        synchronized (mRegistrations) {
+            mServiceRegistered = false;
+            mCurrentRequest = null;
+            updateService();
         }
     }
 
@@ -404,9 +397,9 @@
 
     /**
      * Evaluates the predicate on all registrations. The predicate should return true if the active
-     * state of the registration may have changed as a result. Any {@link #updateService()}
-     * invocations made while this method is executing will be deferred until after the method is
-     * complete so as to avoid redundant work.
+     * state of the registration may have changed as a result. If the active state of any
+     * registration has changed, {@link #updateService()} will automatically be invoked to handle
+     * the resulting changes.
      */
     protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
         synchronized (mRegistrations) {
@@ -415,7 +408,6 @@
             // callbacks. note that try-with-resources ordering is meaningful here as well. we want
             // to close the reentrancy guard first, as this may generate additional service updates,
             // then close the update service buffer.
-            long identity = Binder.clearCallingIdentity();
             try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
                  ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
 
@@ -426,8 +418,6 @@
                         onRegistrationActiveChanged(registration);
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
         }
     }
@@ -450,7 +440,10 @@
                     execute(registration, operation);
                 }
             } else {
-                registration.onInactive();
+                ListenerOperation<TListener> operation = registration.onInactive();
+                if (operation != null) {
+                    execute(registration, operation);
+                }
                 if (--mActiveRegistrationsCount == 0) {
                     onInactive();
                 }
@@ -468,7 +461,6 @@
     protected final void deliverToListeners(
             @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
         synchronized (mRegistrations) {
-            long identity = Binder.clearCallingIdentity();
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
@@ -480,8 +472,6 @@
                         }
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
         }
     }
@@ -495,7 +485,6 @@
      */
     protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
         synchronized (mRegistrations) {
-            long identity = Binder.clearCallingIdentity();
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
@@ -504,8 +493,6 @@
                         execute(registration, operation);
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
         }
     }
@@ -522,27 +509,26 @@
     /**
      * Dumps debug information.
      */
-    public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mRegistrations) {
-            ipw.print("service: ");
-            dumpServiceState(ipw);
-            ipw.println();
+            pw.print("service: ");
+            dumpServiceState(pw);
+            pw.println();
 
             if (!mRegistrations.isEmpty()) {
-                ipw.println("listeners:");
+                pw.println("listeners:");
 
-                ipw.increaseIndent();
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
                     TRegistration registration = mRegistrations.valueAt(i);
-                    ipw.print(registration);
+                    pw.print("  ");
+                    pw.print(registration);
                     if (!registration.isActive()) {
-                        ipw.println(" (inactive)");
+                        pw.println(" (inactive)");
                     } else {
-                        ipw.println();
+                        pw.println();
                     }
                 }
-                ipw.decreaseIndent();
             }
         }
     }
@@ -577,7 +563,7 @@
         @GuardedBy("mRegistrations")
         private int mGuardCount;
         @GuardedBy("mRegistrations")
-        private @Nullable ArraySet<Pair<Object, ListenerRegistration<?, ?>>> mScheduledRemovals;
+        private @Nullable ArraySet<Entry<Object, ListenerRegistration<?, ?>>> mScheduledRemovals;
 
         ReentrancyGuard() {
             mGuardCount = 0;
@@ -602,7 +588,7 @@
             if (mScheduledRemovals == null) {
                 mScheduledRemovals = new ArraySet<>(mRegistrations.size());
             }
-            mScheduledRemovals.add(new Pair<>(key, registration));
+            mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
         }
 
         ReentrancyGuard acquire() {
@@ -612,7 +598,7 @@
 
         @Override
         public void close() {
-            ArraySet<Pair<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null;
+            ArraySet<Entry<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null;
 
             Preconditions.checkState(mGuardCount > 0);
             if (--mGuardCount == 0) {
@@ -620,14 +606,15 @@
                 mScheduledRemovals = null;
             }
 
-            if (scheduledRemovals != null) {
-                try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
-                    final int size = scheduledRemovals.size();
-                    for (int i = 0; i < size; i++) {
-                        Pair<Object, ListenerRegistration<?, ?>> pair = scheduledRemovals.valueAt(
-                                i);
-                        removeRegistration(pair.first, pair.second);
-                    }
+            if (scheduledRemovals == null) {
+                return;
+            }
+
+            try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
+                final int size = scheduledRemovals.size();
+                for (int i = 0; i < size; i++) {
+                    Entry<Object, ListenerRegistration<?, ?>> entry = scheduledRemovals.valueAt(i);
+                    removeRegistration(entry.getKey(), entry.getValue());
                 }
             }
         }
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 ac56c51..deb9660 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -17,55 +17,34 @@
 package com.android.server.location.listeners;
 
 
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-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;
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
  * A listener registration object which holds data associated with the listener, such as an optional
- * request, and the identity of the listener owner.
+ * request, and an executor responsible for listener invocations.
  *
  * @param <TRequest>  request type
  * @param <TListener> listener type
  */
 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;
 
     private boolean mActive;
 
     private volatile @Nullable TListener mListener;
 
-    protected ListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
+    protected ListenerRegistration(Executor executor, @Nullable TRequest request,
             TListener listener) {
-        // if a client is in the same process as us, binder calls will execute synchronously and
-        // we shouldn't run callbacks directly since they might be run under lock and deadlock
-        if (identity.getPid() == Process.myPid()) {
-            // 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 = IN_PROCESS_EXECUTOR;
-        } else {
-            mExecutor = DIRECT_EXECUTOR;
-        }
-
+        mExecutor = Objects.requireNonNull(executor);
         mRequest = request;
-        mIdentity = Objects.requireNonNull(identity);
         mActive = false;
         mListener = Objects.requireNonNull(listener);
     }
@@ -82,34 +61,34 @@
     }
 
     /**
-     * Returns the listener identity.
-     */
-    public final CallerIdentity getIdentity() {
-        return mIdentity;
-    }
-
-    /**
-     * May be overridden by subclasses. Invoked when registration occurs.
+     * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
+     * owning multiplexer's internal lock.
      */
     protected void onRegister(Object key) {}
 
     /**
-     * May be overridden by subclasses. Invoked when unregistration occurs.
+     * May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
+     * the owning multiplexer's internal lock.
      */
     protected void onUnregister() {}
 
     /**
      * May be overridden by subclasses. Invoked when this registration becomes active. If this
-     * returns a non-null operation, that operation will be invoked for the listener.
+     * returns a non-null operation, that operation will be invoked for the listener. Invoked
+     * while holding the owning multiplexer's internal lock.
      */
     protected @Nullable ListenerOperation<TListener> onActive() {
         return null;
     }
 
     /**
-     * May be overridden by subclasses. Invoked when registration becomes inactive.
+     * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
+     * a non-null operation, that operation will be invoked for the listener. Invoked while holding
+     * the owning multiplexer's internal lock.
      */
-    protected void onInactive() {}
+    protected @Nullable ListenerOperation<TListener> onInactive() {
+        return null;
+    }
 
     public final boolean isActive() {
         return mActive;
@@ -136,8 +115,7 @@
     /**
      * May be overridden by subclasses, however should rarely be needed. Invoked when the listener
      * associated with this registration is unregistered, which may occur before the registration
-     * itself is unregistered. This immediately prevents the listener from being further invoked
-     * even if the various bookkeeping associated with unregistration has not occurred yet.
+     * itself is unregistered. This immediately prevents the listener from being further invoked.
      */
     protected void onListenerUnregister() {};
 
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index b5d2ef6..7b6154e 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -30,7 +30,7 @@
  * @param <TListener> listener type
  */
 public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
-        RemovableListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+        RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
 
     /**
      * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
new file mode 100644
index 0000000..e4b0b19
--- /dev/null
+++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.listeners;
+
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import android.annotation.Nullable;
+import android.location.util.identity.CallerIdentity;
+import android.os.Process;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.FgThread;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A listener registration representing a remote (possibly from a different process) listener.
+ * Listeners from a different process will be run on a direct executor, since the x-process listener
+ * invocation should already be asynchronous. Listeners from the same process will be run on a
+ * normal executor, since in-process listener invocation may be synchronous.
+ *
+ * @param <TRequest>  request type
+ * @param <TListener> listener type
+ */
+public abstract class RemoteListenerRegistration<TRequest, TListener> extends
+        RemovableListenerRegistration<TRequest, TListener> {
+
+    @VisibleForTesting
+    public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
+
+    private static Executor chooseExecutor(CallerIdentity identity) {
+        // if a client is in the same process as us, binder calls will execute synchronously and
+        // we shouldn't run callbacks directly since they might be run under lock and deadlock
+        if (identity.getPid() == Process.myPid()) {
+            // 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
+            return IN_PROCESS_EXECUTOR;
+        } else {
+            return DIRECT_EXECUTOR;
+        }
+    }
+
+    private final CallerIdentity mIdentity;
+
+    protected RemoteListenerRegistration(String tag, @Nullable TRequest request,
+            CallerIdentity identity, TListener listener) {
+        super(tag, chooseExecutor(identity), request, listener);
+        mIdentity = Objects.requireNonNull(identity);
+    }
+
+    /**
+     * Returns the listener identity.
+     */
+    public final CallerIdentity getIdentity() {
+        return mIdentity;
+    }
+}
+
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 0698cca..2383bec 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -17,10 +17,10 @@
 package com.android.server.location.listeners;
 
 import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
 import android.util.Log;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * A listener registration that stores its own key, and thus can remove itself. By default it will
@@ -36,9 +36,9 @@
 
     private volatile @Nullable Object mKey;
 
-    protected RemovableListenerRegistration(String tag, @Nullable TRequest request,
-            CallerIdentity callerIdentity, TListener listener) {
-        super(request, callerIdentity, listener);
+    protected RemovableListenerRegistration(String tag, Executor executor,
+            @Nullable TRequest request, TListener listener) {
+        super(executor, request, listener);
         mTag = Objects.requireNonNull(tag);
     }
 
@@ -70,7 +70,7 @@
 
     @Override
     public <Listener> void onOperationFailure(ListenerOperation<Listener> operation, Exception e) {
-        Log.w(mTag, "registration " + getIdentity() + " removed due to unexpected exception", e);
+        Log.w(mTag, "registration " + this + " removed due to unexpected exception", e);
         remove();
     }
 
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 d6e37bac..26c3132 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -33,11 +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_RETURN_GK_PW;
+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;
@@ -105,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;
 
@@ -124,7 +124,6 @@
 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.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
@@ -156,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;
@@ -187,22 +187,13 @@
     private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
-    // TODO (b/145978626) LockSettingsService no longer accepts challenges in the verifyCredential
-    //  paths. These are temporarily left around to ensure that resetLockout works. It will be
-    //  removed once resetLockout is compartmentalized.
-    // 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.
@@ -219,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;
@@ -321,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 {
@@ -585,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);
@@ -740,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 */);
         }
     }
 
@@ -830,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() {
@@ -1041,7 +1030,7 @@
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
     }
 
-    private final void checkPasswordReadPermission(int userId) {
+    private final void checkPasswordReadPermission() {
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
     }
 
@@ -1101,7 +1090,9 @@
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
             LockscreenCredential managedUserPassword) {
         checkWritePermission(userId);
-        if (!mHasSecureLockScreen) {
+        if (!mHasSecureLockScreen
+                && managedUserPassword != null
+                && managedUserPassword.getType() != CREDENTIAL_TYPE_NONE) {
             throw new UnsupportedOperationException(
                     "This operation requires secure lock screen feature.");
         }
@@ -1306,13 +1297,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, 0 /* flags */);
+                    profileHandle, null /* progressCallback */, 0 /* flags */);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1327,10 +1315,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.
@@ -1339,9 +1323,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
@@ -1378,6 +1360,9 @@
         }
 
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+            if (!hasUnifiedChallenge(userId)) {
+                mBiometricDeferredQueue.processPendingLockoutResets();
+            }
             return;
         }
 
@@ -1388,10 +1373,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
@@ -1412,22 +1394,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) {
@@ -1593,7 +1562,8 @@
     public boolean setLockCredential(LockscreenCredential credential,
             LockscreenCredential savedCredential, int userId) {
 
-        if (!mHasSecureLockScreen) {
+        if (!mHasSecureLockScreen
+                && credential != null && credential.getType() != CREDENTIAL_TYPE_NONE) {
             throw new UnsupportedOperationException(
                     "This operation requires secure lock screen feature");
         }
@@ -1727,8 +1697,7 @@
         setUserKeyProtection(userId, credential, convertResponse(gkResponse));
         fixateNewestUserKeyAuth(userId);
         // Refresh the auth token
-        doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
-                null /* progressCallback */, 0 /* flags */);
+        doVerifyCredential(credential, userId, null /* progressCallback */, 0 /* flags */);
         synchronizeUnifiedWorkChallengeForProfiles(userId, null);
         sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
@@ -1970,10 +1939,9 @@
     @Override
     public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId, progressCallback,
-                    0 /* flags */);
+            return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
         } finally {
             scheduleGc();
         }
@@ -1983,55 +1951,56 @@
     @Nullable
     public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
             int userId, int flags) {
-        checkPasswordReadPermission(userId);
-
+        checkPasswordReadPermission();
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0L, userId,
-                    null /* progressCallback */, flags);
+            return doVerifyCredential(credential, userId, null /* progressCallback */, flags);
         } finally {
             scheduleGc();
         }
     }
 
     @Override
-    public VerifyCredentialResponse verifyGatekeeperPassword(byte[] gatekeeperPassword,
+    public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
             long challenge, int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
 
-        VerifyCredentialResponse response;
+        final VerifyCredentialResponse response;
+        final byte[] gatekeeperPassword;
+
+        synchronized (mGatekeeperPasswords) {
+            gatekeeperPassword = mGatekeeperPasswords.get(gatekeeperPasswordHandle);
+        }
+
         synchronized (mSpManager) {
-            response = mSpManager.verifyChallengeInternal(getGateKeeperService(),
-                    gatekeeperPassword, challenge, userId);
+            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 challengeType Owner of the challenge
-     * @param challenge Challenge to be wrapped within Gatekeeper's HAT, if the credential is
-     *                  verified
      * @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,
-            @LockPatternUtils.VerifyFlag int flags) {
-        return doVerifyCredential(credential, challengeType, challenge, userId,
-                progressCallback, null /* resetLockouts */, flags);
-    }
-
-    /**
-     * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
-     * format.
-     */
-    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");
@@ -2041,9 +2010,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, flags);
+
+        VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
+                progressCallback, flags);
+
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2064,8 +2034,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);
@@ -2077,7 +2046,7 @@
     @Override
     public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
             int userId, @LockPatternUtils.VerifyFlag int flags) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         if (!isManagedProfileWithUnifiedLock(userId)) {
             throw new IllegalArgumentException("User id must be managed profile with unified lock");
         }
@@ -2085,8 +2054,6 @@
         // Unlock parent by using parent's challenge
         final VerifyCredentialResponse parentResponse = doVerifyCredential(
                 credential,
-                CHALLENGE_NONE,
-                0L,
                 parentProfileId,
                 null /* progressCallback */,
                 flags);
@@ -2098,10 +2065,7 @@
         try {
             // Unlock work profile, and work profile with unified lock must use password only
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
-                    CHALLENGE_NONE,
-                    0L,
-                    userId, null /* progressCallback */,
-                    flags);
+                    userId, null /* progressCallback */, flags);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -2119,8 +2083,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;
@@ -2137,7 +2100,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;
@@ -2250,7 +2213,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
@@ -2340,13 +2303,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);
     }
 
@@ -2376,7 +2339,7 @@
 
     @Override
     public int getStrongAuthForUser(int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         return mStrongAuthTracker.getStrongAuthForUser(userId);
     }
 
@@ -2710,31 +2673,16 @@
     }
 
     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 returnGkPw = (flags & VERIFY_FLAG_RETURN_GK_PW) != 0;
+        final boolean requestGkPw = (flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0;
 
         synchronized (mSpManager) {
             if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
@@ -2752,10 +2700,13 @@
 
             // 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.
@@ -2765,15 +2716,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.getGatekeeperHAT()));
-            }
-
-            onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
+            onCredentialVerified(authResult.authToken,
                     PasswordMetrics.computeForCredential(userCredential), userId);
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
@@ -2781,17 +2724,44 @@
             }
         }
 
-        if (response.isMatched() && returnGkPw) {
-            return new VerifyCredentialResponse.Builder()
-                    .setGatekeeperPassword(authResult.authToken.deriveGkPassword()).build();
+        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) {
@@ -2806,7 +2776,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);
@@ -3028,7 +2998,7 @@
      */
     @Override
     public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         try {
             if (isManagedProfileWithUnifiedLock(userId)) {
                 try {
@@ -3106,7 +3076,7 @@
 
     @Override
     public boolean hasPendingEscrowToken(int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         synchronized (mSpManager) {
             return !mSpManager.getPendingTokensForUser(userId).isEmpty();
         }
@@ -3193,11 +3163,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;
     }
@@ -3208,8 +3175,7 @@
             if (cred == null) {
                 return false;
             }
-            return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId,
-                    null /* progressCallback */, 0 /* flags */)
+            return doVerifyCredential(cred, userId, null /* progressCallback */, 0 /* flags */)
                     .getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
         }
     }
@@ -3296,6 +3262,8 @@
         mRebootEscrowManager.dump(pw);
         pw.println();
         pw.decreaseIndent();
+
+        pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
     }
 
     /**
@@ -3458,7 +3426,8 @@
         @Override
         public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
                 byte[] token, int userId) {
-            if (!mHasSecureLockScreen) {
+        if (!mHasSecureLockScreen
+                && credential != null && credential.getType() != CREDENTIAL_TYPE_NONE) {
                 throw new UnsupportedOperationException(
                         "This operation requires secure lock screen feature.");
             }
@@ -3532,8 +3501,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 e9a05a8..715e41c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -746,7 +746,7 @@
 
     public void dump(IndentingPrintWriter pw) {
         final UserManager um = UserManager.get(mContext);
-        for (UserInfo user : um.getUsers(false)) {
+        for (UserInfo user : um.getUsers()) {
             File userPath = getSyntheticPasswordDirectoryForUser(user.id);
             pw.println(String.format("User %d [%s]:", user.id, userPath.getAbsolutePath()));
             pw.increaseIndent();
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/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/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 8777cea..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;
@@ -905,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 a8a0e2e..9521611 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1918,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
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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 295143e..29ee8eb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4367,7 +4367,7 @@
                 // Flip state because app was explicitly added or removed to denylist.
                 setMeteredNetworkDenylist(uid, (isDenylisted || isRestrictedByAdmin));
                 if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowlisted) {
-                    // Since dneylist prevails over allowlist, we need to handle the special case
+                    // 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);
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/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index a604625..74b7bd7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1554,7 +1554,7 @@
             if (!isEnabledForCurrentProfiles()) {
                 return false;
             }
-            return this.userid == userId;
+            return userId == USER_ALL || userId == this.userid;
         }
 
         public boolean enabledAndUserMatches(int nid) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9f9235d..12419a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -262,7 +262,6 @@
 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;
@@ -966,8 +965,7 @@
                     nv.recycle();
                 }
                 reportUserInteraction(r);
-                mAssistants.notifyAssistantActionClicked(
-                        r.getSbn(), actionIndex, action, generatedByAssistant);
+                mAssistants.notifyAssistantActionClicked(r.getSbn(), action, generatedByAssistant);
             }
         }
 
@@ -6103,7 +6101,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;
@@ -8628,12 +8627,25 @@
                 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
     }
 
-    private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
+    @VisibleForTesting
+    boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
         if (!listener.enabledAndUserMatches(sbn.getUserId())) {
             return false;
         }
-        // TODO: remove this for older listeners.
-        return true;
+        return isInteractionVisibleToListener(listener, sbn.getUserId());
+    }
+
+    /**
+     * Returns whether the given assistant should be informed about interactions on the given user.
+     *
+     * Normally an assistant would be able to see all interactions on the current user and any
+     * associated profiles because they are notification listeners, but since NASes have one
+     * instance per user, we want to filter out interactions that are not for the user that the
+     * given NAS is bound in.
+     */
+    private boolean isInteractionVisibleToListener(ManagedServiceInfo info, int userId) {
+        boolean isAssistantService = mAssistants.isServiceTokenValidLocked(info.service);
+        return !isAssistantService || info.isSameUser(userId);
     }
 
     private boolean isPackageSuspendedForUser(String pkg, int uid) {
@@ -8855,8 +8867,6 @@
         }
 
         protected void onNotificationsSeenLocked(ArrayList<NotificationRecord> records) {
-            // There should be only one, but it's a list, so while we enforce
-            // singularity elsewhere, we keep it general here, to avoid surprises.
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 ArrayList<String> keys = new ArrayList<>(records.size());
                 for (NotificationRecord r : records) {
@@ -8874,6 +8884,8 @@
         }
 
         protected void onPanelRevealed(int items) {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the panel
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener assistant = (INotificationListener) info.service;
@@ -8887,6 +8899,8 @@
         }
 
         protected void onPanelHidden() {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the panel
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener assistant = (INotificationListener) info.service;
@@ -8975,7 +8989,7 @@
             }
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationVisibilityChanged(key, isVisible);
@@ -8993,7 +9007,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationExpansionChanged(key, isUserAction, isExpanded);
@@ -9009,7 +9023,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationDirectReply(key);
@@ -9025,7 +9039,7 @@
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onSuggestedReplySent(
@@ -9042,12 +9056,12 @@
 
         @GuardedBy("mNotificationLock")
         void notifyAssistantActionClicked(
-                final StatusBarNotification sbn, int actionIndex, Notification.Action action,
+                final StatusBarNotification sbn, Notification.Action action,
                 boolean generatedByAssistant) {
             final String key = sbn.getKey();
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onActionClicked(
@@ -9071,7 +9085,7 @@
                 final StatusBarNotification sbn, final String snoozeCriterionId) {
             notifyAssistantLocked(
                     sbn,
-                    false /* sameUserOnly */,
+                    true /* sameUserOnly */,
                     (assistant, sbnHolder) -> {
                         try {
                             assistant.onNotificationSnoozedUntilContext(
@@ -9128,7 +9142,7 @@
         }
 
         protected void resetDefaultAssistantsIfNecessary() {
-            final List<UserInfo> activeUsers = mUm.getUsers(true);
+            final List<UserInfo> activeUsers = mUm.getAliveUsers();
             for (UserInfo userInfo : activeUsers) {
                 int userId = userInfo.getUserHandle().getIdentifier();
                 if (!hasUserSet(userId)) {
@@ -9292,10 +9306,12 @@
         }
 
         public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
+            // send to all currently bounds NASes since notifications from both users will appear in
+            // the status bar
             for (final ManagedServiceInfo info : getServices()) {
                 mHandler.post(() -> {
                     final INotificationListener listener = (INotificationListener) info.service;
-                     try {
+                    try {
                         listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
                     } catch (RemoteException ex) {
                         Slog.e(TAG, "unable to notify listener "
@@ -9469,7 +9485,8 @@
                     && changedHiddenNotifications.size() > 0;
 
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
 
@@ -9488,12 +9505,7 @@
                     final NotificationRankingUpdate update = makeRankingUpdateLocked(
                             serviceInfo);
 
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            notifyRankingUpdate(serviceInfo, update);
-                        }
-                    });
+                    mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
                 }
             }
         }
@@ -9501,15 +9513,11 @@
         @GuardedBy("mNotificationLock")
         public void notifyListenerHintsChangedLocked(final int hints) {
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyListenerHintsChanged(serviceInfo, hints);
-                    }
-                });
+                mHandler.post(() -> notifyListenerHintsChanged(serviceInfo, hints));
             }
         }
 
@@ -9561,15 +9569,12 @@
 
         public void notifyInterruptionFilterChanged(final int interruptionFilter) {
             for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
+                        serviceInfo, ActivityManager.getCurrentUser())) {
                     continue;
                 }
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyInterruptionFilterChanged(serviceInfo, interruptionFilter);
-                    }
-                });
+                mHandler.post(
+                        () -> notifyInterruptionFilterChanged(serviceInfo, interruptionFilter));
             }
         }
 
@@ -9578,15 +9583,16 @@
             if (channel == null) {
                 return;
             }
-            for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+            for (final ManagedServiceInfo info : getServices()) {
+                if (!info.enabledAndUserMatches(UserHandle.getCallingUserId())
+                        || !isInteractionVisibleToListener(info, UserHandle.getCallingUserId())) {
                     continue;
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
+                    if (info.isSystem || hasCompanionDevice(info)) {
                         notifyNotificationChannelChanged(
-                                serviceInfo, pkg, user, channel, modificationType);
+                                info, pkg, user, channel, modificationType);
                     }
                 });
             }
@@ -9598,15 +9604,16 @@
             if (group == null) {
                 return;
             }
-            for (final ManagedServiceInfo serviceInfo : getServices()) {
-                if (!serviceInfo.enabledAndUserMatches(UserHandle.getCallingUserId())) {
+            for (final ManagedServiceInfo info : getServices()) {
+                if (!info.enabledAndUserMatches(UserHandle.getCallingUserId())
+                        || !isInteractionVisibleToListener(info, UserHandle.getCallingUserId())) {
                     continue;
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) {
+                    if (info.isSystem || hasCompanionDevice(info)) {
                         notifyNotificationChannelGroupChanged(
-                                serviceInfo, pkg, user, group, modificationType);
+                                info, pkg, user, group, modificationType);
                     }
                 });
             }
@@ -9625,9 +9632,6 @@
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
                 NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
-            if (!info.enabledAndUserMatches(sbn.getUserId())) {
-                return;
-            }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a4debc1..d7a1ba2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -288,7 +288,7 @@
 
     private void initIfNeeded() {
         final UserManager um = getContext().getSystemService(UserManager.class);
-        final List<UserInfo> users = um.getUsers(true /*excludeDying*/);
+        final List<UserInfo> users = um.getAliveUsers();
         synchronized (mLock) {
             final int userCount = users.size();
             for (int i = 0; i < userCount; i++) {
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
new file mode 100644
index 0000000..d8745ab
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.PackageManager.WHOLE_MD5;
+import static android.content.pm.PackageManager.WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.PackageManager.WHOLE_SHA1;
+import static android.content.pm.PackageManager.WHOLE_SHA256;
+import static android.content.pm.PackageManager.WHOLE_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
+
+import android.annotation.Nullable;
+import android.content.pm.FileChecksum;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+import android.util.apk.ApkSignatureSchemeV4Verifier;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
+import android.util.apk.ByteBufferFactory;
+import android.util.apk.SignatureInfo;
+import android.util.apk.SignatureNotFoundException;
+import android.util.apk.VerityBuilder;
+
+import com.android.server.security.VerityUtils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides checksums for APK.
+ */
+public class ApkChecksums {
+    static final String TAG = "ApkChecksums";
+
+    // MessageDigest algorithms.
+    static final String ALGO_MD5 = "MD5";
+    static final String ALGO_SHA1 = "SHA1";
+    static final String ALGO_SHA256 = "SHA256";
+    static final String ALGO_SHA512 = "SHA512";
+
+    /**
+     * Fetch or calculate checksums for the specific file.
+     *
+     * @param split             split name, null for base
+     * @param file              to fetch checksums for
+     * @param optional          mask to fetch readily available checksums
+     * @param required          mask to forcefully calculate if not available
+     * @param trustedInstallers array of certificate to trust, two specific cases:
+     *                          null - trust anybody,
+     *                          [] - trust nobody.
+     */
+    public static List<FileChecksum> getFileChecksums(String split, File file,
+            @PackageManager.FileChecksumKind int optional,
+            @PackageManager.FileChecksumKind int required,
+            @Nullable Certificate[] trustedInstallers) {
+        final String filePath = file.getAbsolutePath();
+        Map<Integer, FileChecksum> checksums = new ArrayMap<>();
+        final int kinds = (optional | required);
+        // System enforced: FSI or v2/v3/v4 signatures.
+        if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) {
+            // Hashes in fs-verity and IncFS are always verified.
+            FileChecksum checksum = extractHashFromFS(split, filePath);
+            if (checksum != null) {
+                checksums.put(checksum.getKind(), checksum);
+            }
+        }
+        if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) {
+            Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
+                    split, filePath, kinds);
+            if (v2v3checksums != null) {
+                checksums.putAll(v2v3checksums);
+            }
+        }
+
+        // TODO(b/160605420): Installer provided.
+        // TODO(b/160605420): Wait for Incremental to be fully loaded.
+
+        // Manually calculating required checksums if not readily available.
+        if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey(
+                WHOLE_MERKLE_ROOT_4K_SHA256)) {
+            try {
+                byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
+                        filePath, /*salt=*/null,
+                        new ByteBufferFactory() {
+                            @Override
+                            public ByteBuffer create(int capacity) {
+                                return ByteBuffer.allocate(capacity);
+                            }
+                        });
+                checksums.put(WHOLE_MERKLE_ROOT_4K_SHA256,
+                        new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, generatedRootHash));
+            } catch (IOException | NoSuchAlgorithmException | DigestException e) {
+                Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
+            }
+        }
+
+        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5);
+        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1);
+        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256);
+        calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512);
+
+        calculatePartialChecksumsIfRequested(checksums, split, file, required);
+
+        return new ArrayList<>(checksums.values());
+    }
+
+    private static FileChecksum extractHashFromFS(String split, String filePath) {
+        // verity first
+        {
+            byte[] hash = VerityUtils.getFsverityRootHash(filePath);
+            if (hash != null) {
+                return new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
+            }
+        }
+        // v4 next
+        try {
+            ApkSignatureSchemeV4Verifier.VerifiedSigner signer =
+                    ApkSignatureSchemeV4Verifier.extractCertificates(filePath);
+            byte[] hash = signer.contentDigests.getOrDefault(CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
+                    null);
+            if (hash != null) {
+                return new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
+            }
+        } catch (SignatureNotFoundException e) {
+            // Nothing
+        } catch (SecurityException e) {
+            Slog.e(TAG, "V4 signature error", e);
+        }
+        return null;
+    }
+
+    private static Map<Integer, FileChecksum> extractHashFromV2V3Signature(
+            String split, String filePath, int kinds) {
+        Map<Integer, byte[]> contentDigests = null;
+        try {
+            contentDigests = ApkSignatureVerifier.verifySignaturesInternal(filePath,
+                    PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
+                    false).contentDigests;
+        } catch (PackageParser.PackageParserException e) {
+            Slog.e(TAG, "Signature verification error", e);
+        }
+
+        if (contentDigests == null) {
+            return null;
+        }
+
+        Map<Integer, FileChecksum> checksums = new ArrayMap<>();
+        if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0) {
+            byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA256, null);
+            if (hash != null) {
+                checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA256,
+                        new FileChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
+            }
+        }
+        if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0) {
+            byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA512, null);
+            if (hash != null) {
+                checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA512,
+                        new FileChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
+            }
+        }
+        return checksums;
+    }
+
+    private static String getMessageDigestAlgoForChecksumKind(int kind)
+            throws NoSuchAlgorithmException {
+        switch (kind) {
+            case WHOLE_MD5:
+                return ALGO_MD5;
+            case WHOLE_SHA1:
+                return ALGO_SHA1;
+            case WHOLE_SHA256:
+                return ALGO_SHA256;
+            case WHOLE_SHA512:
+                return ALGO_SHA512;
+            default:
+                throw new NoSuchAlgorithmException("Invalid checksum kind: " + kind);
+        }
+    }
+
+    private static void calculateChecksumIfRequested(Map<Integer, FileChecksum> checksums,
+            String split, File file, int required, int kind) {
+        if ((required & kind) != 0 && !checksums.containsKey(kind)) {
+            final byte[] checksum = getFileChecksum(file, kind);
+            if (checksum != null) {
+                checksums.put(kind, new FileChecksum(split, kind, checksum));
+            }
+        }
+    }
+
+    private static byte[] getFileChecksum(File file, int kind) {
+        try (FileInputStream fis = new FileInputStream(file);
+             BufferedInputStream bis = new BufferedInputStream(fis)) {
+            byte[] dataBytes = new byte[512 * 1024];
+            int nread = 0;
+
+            final String algo = getMessageDigestAlgoForChecksumKind(kind);
+            MessageDigest md = MessageDigest.getInstance(algo);
+            while ((nread = bis.read(dataBytes)) != -1) {
+                md.update(dataBytes, 0, nread);
+            }
+
+            return md.digest();
+        } catch (IOException e) {
+            Slog.e(TAG, "Error reading " + file.getAbsolutePath() + " to compute hash.", e);
+            return null;
+        } catch (NoSuchAlgorithmException e) {
+            Slog.e(TAG, "Device does not support MessageDigest algorithm", e);
+            return null;
+        }
+    }
+
+    private static int[] getContentDigestAlgos(boolean needSignatureSha256,
+            boolean needSignatureSha512) {
+        if (needSignatureSha256 && needSignatureSha512) {
+            // Signature block present, but no digests???
+            return new int[]{CONTENT_DIGEST_CHUNKED_SHA256, CONTENT_DIGEST_CHUNKED_SHA512};
+        } else if (needSignatureSha256) {
+            return new int[]{CONTENT_DIGEST_CHUNKED_SHA256};
+        } else {
+            return new int[]{CONTENT_DIGEST_CHUNKED_SHA512};
+        }
+    }
+
+    private static int getChecksumKindForContentDigestAlgo(int contentDigestAlgo) {
+        switch (contentDigestAlgo) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return PARTIAL_MERKLE_ROOT_1M_SHA256;
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return PARTIAL_MERKLE_ROOT_1M_SHA512;
+            default:
+                return -1;
+        }
+    }
+
+    private static void calculatePartialChecksumsIfRequested(Map<Integer, FileChecksum> checksums,
+            String split, File file, int required) {
+        boolean needSignatureSha256 =
+                (required & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
+                        PARTIAL_MERKLE_ROOT_1M_SHA256);
+        boolean needSignatureSha512 =
+                (required & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0 && !checksums.containsKey(
+                        PARTIAL_MERKLE_ROOT_1M_SHA512);
+        if (!needSignatureSha256 && !needSignatureSha512) {
+            return;
+        }
+
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
+            SignatureInfo signatureInfo = null;
+            try {
+                signatureInfo = ApkSignatureSchemeV3Verifier.findSignature(raf);
+            } catch (SignatureNotFoundException e) {
+                try {
+                    signatureInfo = ApkSignatureSchemeV2Verifier.findSignature(raf);
+                } catch (SignatureNotFoundException ee) {
+                }
+            }
+            if (signatureInfo == null) {
+                Slog.e(TAG, "V2/V3 signatures not found in " + file.getAbsolutePath());
+                return;
+            }
+
+            final int[] digestAlgos = getContentDigestAlgos(needSignatureSha256,
+                    needSignatureSha512);
+            byte[][] digests = ApkSigningBlockUtils.computeContentDigestsPer1MbChunk(digestAlgos,
+                    raf.getFD(), signatureInfo);
+            for (int i = 0, size = digestAlgos.length; i < size; ++i) {
+                int checksumKind = getChecksumKindForContentDigestAlgo(digestAlgos[i]);
+                if (checksumKind != -1) {
+                    checksums.put(checksumKind, new FileChecksum(split, checksumKind, digests[i]));
+                }
+            }
+        } catch (IOException | DigestException e) {
+            Slog.e(TAG, "Error computing hash.", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index def9c78f..3d7c978 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -35,9 +35,6 @@
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedProvider;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.HandlerThread;
 import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -353,13 +350,9 @@
                         injector.getUserManagerInternal().getUserInfos());
             }
         };
-        HandlerThread appsFilterThread = new HandlerThread("appsFilter");
-        appsFilterThread.start();
-        Handler appsFilterHandler = new Handler(appsFilterThread.getLooper());
-        Executor executor = new HandlerExecutor(appsFilterHandler);
-
         AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
-                forcedQueryablePackageNames, forceSystemAppsQueryable, null, executor);
+                forcedQueryablePackageNames, forceSystemAppsQueryable, null,
+                injector.getBackgroundExecutor());
         featureConfig.setAppsFilter(appsFilter);
         return appsFilter;
     }
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 617f687..a234f5a 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -605,7 +605,7 @@
     private boolean isPlatformSignedAppWithAutomaticProfilesPermission(
             String packageName, int[] profileIds) {
         for (int userId : profileIds) {
-            final int uid = mInjector.getPackageManagerInternal().getPackageUidInternal(
+            final int uid = mInjector.getPackageManagerInternal().getPackageUid(
                     packageName, /* flags= */ 0, userId);
             if (uid == -1) {
                 continue;
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/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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8af7463..4b246c3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1284,14 +1284,21 @@
             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);
 
-                // Do not print finalized staged session as active install sessions
                 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;
@@ -1303,6 +1310,21 @@
             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 bc090f0..ed62362 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -134,6 +134,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.dex.DexManager;
@@ -1325,6 +1326,7 @@
                 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) {
@@ -1341,10 +1343,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);
                 }
             });
         }
@@ -1554,12 +1555,28 @@
     }
 
     private void onSessionValidationFailure(int error, String detailMessage) {
-        // Session is sealed but could not be verified, we need to destroy it.
+        // Session is sealed but could not be validated, we need to destroy it.
         destroyInternal();
         // Dispatch message to remove session from PackageInstallerService.
         dispatchSessionFinished(error, detailMessage, null);
     }
 
+    private void onSessionVerificationFailure(int error, String detailMessage) {
+        Slog.e(TAG, "Failed to verify session " + sessionId + " [" + detailMessage + "]");
+        // Session is sealed and committed but could not be verified, we need to destroy it.
+        destroyInternal();
+        if (isStaged()) {
+            setStagedSessionFailed(
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, detailMessage);
+            // TODO(b/136257624): Remove this once all verification logic has been transferred out
+            //  of StagingManager.
+            mStagingManager.notifyVerificationComplete(sessionId);
+        } else {
+            // Dispatch message to remove session from PackageInstallerService.
+            dispatchSessionFinished(error, detailMessage, null);
+        }
+    }
+
     private void onStorageUnhealthy() {
         final String packageName = getPackageName();
         if (TextUtils.isEmpty(packageName)) {
@@ -1680,7 +1697,8 @@
         }
         if (params.isStaged) {
             mStagingManager.commitSession(this);
-            destroyInternal();
+            // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
+            //  though ideally, we just need to send session committed broadcast.
             dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
             return;
         }
@@ -1691,14 +1709,30 @@
                     "APEX packages can only be installed using staged sessions.", null);
             return;
         }
+        verify();
+    }
 
+    /**
+     * Resumes verification process for non-final committed staged session.
+     *
+     * Useful if a device gets rebooted before verification is complete and we need to restart the
+     * verification.
+     */
+    void verifyStagedSession() {
+        assertCallerIsOwnerOrRootOrSystemLocked();
+        Preconditions.checkArgument(isCommitted());
+        Preconditions.checkArgument(isStaged());
+        Preconditions.checkArgument(!mStagedSessionApplied && !mStagedSessionFailed);
+
+        verify();
+    }
+
+    private void verify() {
         try {
             verifyNonStaged();
         } catch (PackageManagerException e) {
             final String completeMsg = ExceptionUtils.getCompleteMessage(e);
-            Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
-            destroyInternal();
-            dispatchSessionFinished(e.error, completeMsg, null);
+            onSessionVerificationFailure(e.error, completeMsg);
         }
     }
 
@@ -1846,7 +1880,9 @@
     @GuardedBy("mLock")
     private PackageManagerService.VerificationParams makeVerificationParamsLocked()
             throws PackageManagerException {
-        if (!params.isMultiPackage) {
+        // TODO(b/136257624): Some logic in this if block probably belongs in
+        //  makeInstallParams().
+        if (!params.isMultiPackage && !isApexInstallation()) {
             Objects.requireNonNull(mPackageName);
             Objects.requireNonNull(mSigningDetails);
             Objects.requireNonNull(mResolvedBaseFile);
@@ -1923,8 +1959,7 @@
                     if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
                         onVerificationComplete();
                     } else {
-                        destroyInternal();
-                        dispatchSessionFinished(returnCode, msg, extras);
+                        onSessionVerificationFailure(returnCode, msg);
                     }
                 }
             };
@@ -1946,9 +1981,13 @@
     }
 
     private void onVerificationComplete() {
-        if ((params.installFlags & PackageManager.INSTALL_DRY_RUN) != 0) {
-            destroyInternal();
-            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Dry run", new Bundle());
+        // Staged sessions will be installed later during boot
+        if (isStaged()) {
+            // TODO(b/136257624): Remove this once all verification logic has been transferred out
+            //  of StagingManager.
+            mStagingManager.notifyPreRebootVerification_Apk_Complete(sessionId);
+            // TODO(b/136257624): We also need to destroy internals for verified staged session,
+            //  otherwise file descriptors are never closed for verified staged session until reboot
             return;
         }
 
@@ -2734,23 +2773,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.");
-        }
-
+    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.
@@ -2758,26 +2801,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(getChildSessionsLocked());
             }
-
-            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;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b96aaf4..344f9cf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -40,6 +40,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -168,6 +169,7 @@
 import android.content.pm.DataLoaderType;
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
+import android.content.pm.FileChecksum;
 import android.content.pm.IDexModuleRegisterCallback;
 import android.content.pm.IPackageChangeObserver;
 import android.content.pm.IPackageDataObserver;
@@ -248,10 +250,13 @@
 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;
 import android.os.Parcel;
+import android.os.ParcelableException;
 import android.os.PatternMatcher;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -365,7 +370,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;
@@ -393,6 +397,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -403,7 +408,10 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.SecureRandom;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -418,7 +426,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;
@@ -923,6 +933,7 @@
         private final Object mLock;
         private final Installer mInstaller;
         private final Object mInstallLock;
+        private final Executor mBackgroundExecutor;
 
         // ----- producers -----
         private final Singleton<ComponentResolver> mComponentResolverProducer;
@@ -944,6 +955,7 @@
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
+                Executor backgroundExecutor,
                 Producer<ComponentResolver> componentResolverProducer,
                 Producer<PermissionManagerServiceInternal> permissionManagerProducer,
                 Producer<UserManagerService> userManagerProducer,
@@ -965,6 +977,7 @@
             mInstaller = installer;
             mAbiHelper = abiHelper;
             mInstallLock = installLock;
+            mBackgroundExecutor = backgroundExecutor;
             mComponentResolverProducer = new Singleton<>(componentResolverProducer);
             mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
             mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1078,6 +1091,10 @@
         public PlatformCompat getCompatibility() {
             return mPlatformCompatProducer.get(this, mPackageManager);
         }
+
+        public Executor getBackgroundExecutor() {
+            return mBackgroundExecutor;
+        }
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1807,7 +1824,7 @@
                     synchronized (mLock) {
                         removeMessages(WRITE_SETTINGS);
                         removeMessages(WRITE_PACKAGE_RESTRICTIONS);
-                        mSettings.writeLPr();
+                        writeSettingsLPrTEMP();
                         mDirtyUsers.clear();
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -1827,6 +1844,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);
@@ -2389,9 +2407,12 @@
         final int callingUserId = UserHandle.getUserId(callingUid);
 
         for (String packageName : packages) {
-            PackageSetting setting = mSettings.mPackages.get(packageName);
-            if (setting != null
-                    && !shouldFilterApplicationLocked(setting, callingUid, callingUserId)) {
+            final boolean filterApp;
+            synchronized (mLock) {
+                final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                filterApp = shouldFilterApplicationLocked(ps, callingUid, callingUserId);
+            }
+            if (!filterApp) {
                 notifyInstallObserver(packageName);
             }
         }
@@ -2432,6 +2453,83 @@
         mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
     }
 
+    @Override
+    public void getChecksums(@NonNull String packageName, boolean includeSplits,
+            @PackageManager.FileChecksumKind int optional,
+            @PackageManager.FileChecksumKind int required, @Nullable List trustedInstallers,
+            @NonNull IntentSender statusReceiver, int userId) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(statusReceiver);
+
+        final ApplicationInfo applicationInfo = getApplicationInfoInternal(packageName, 0,
+                Binder.getCallingUid(), userId);
+        if (applicationInfo == null) {
+            throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
+        }
+
+        List<Pair<String, File>> filesToChecksum = new ArrayList<>();
+
+        // Adding base split.
+        filesToChecksum.add(Pair.create(null, new File(applicationInfo.sourceDir)));
+
+        // Adding other splits.
+        if (includeSplits && applicationInfo.splitNames != null) {
+            for (int i = 0, size = applicationInfo.splitNames.length; i < size; ++i) {
+                filesToChecksum.add(Pair.create(applicationInfo.splitNames[i],
+                        new File(applicationInfo.splitSourceDirs[i])));
+            }
+        }
+
+        for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+            final File file = filesToChecksum.get(i).second;
+            if (!file.exists()) {
+                throw new IllegalStateException("File not found: " + file.getPath());
+            }
+        }
+
+        final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
+                trustedInstallers) : null;
+        final Context context = mContext;
+
+        mInjector.getBackgroundExecutor().execute(() -> {
+            final Intent intent = new Intent();
+            List<FileChecksum> result = new ArrayList<>();
+            for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+                final String split = filesToChecksum.get(i).first;
+                final File file = filesToChecksum.get(i).second;
+                try {
+                    result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required,
+                            trustedCerts));
+                } catch (Throwable e) {
+                    Slog.e(TAG, "Checksum calculation error", e);
+                }
+            }
+            intent.putExtra(EXTRA_CHECKSUMS,
+                    result.toArray(new FileChecksum[result.size()]));
+
+            try {
+                statusReceiver.sendIntent(context, 1, intent, null, null);
+            } catch (SendIntentException e) {
+                Slog.w(TAG, e);
+            }
+        });
+    }
+
+    private static @NonNull Certificate[] decodeCertificates(@NonNull List certs) {
+        try {
+            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            final Certificate[] result = new Certificate[certs.size()];
+            for (int i = 0, size = certs.size(); i < size; ++i) {
+                final InputStream is = new ByteArrayInputStream((byte[]) certs.get(i));
+                final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+                result[i] = cert;
+            }
+            return result;
+        } catch (CertificateException e) {
+            throw ExceptionUtils.propagate(e);
+        }
+    }
+
     /**
      * Gets the type of the external storage a package is installed on.
      * @param packageVolume The storage volume of the package.
@@ -2507,7 +2605,7 @@
                 }
 
                 mSettings.onVolumeForgotten(fsUuid);
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
     };
@@ -2580,17 +2678,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(),
@@ -3427,6 +3529,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
@@ -3481,6 +3584,7 @@
                     return;
                 }
                 int count = 0;
+                final Installer.Batch batch = new Installer.Batch();
                 for (String pkgName : deferPackages) {
                     AndroidPackage pkg = null;
                     synchronized (mLock) {
@@ -3490,13 +3594,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");
@@ -3544,7 +3649,7 @@
 
             // can downgrade to reader
             t.traceBegin("write settings");
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
             t.traceEnd();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                     SystemClock.uptimeMillis());
@@ -3748,7 +3853,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
@@ -3759,9 +3864,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(),
@@ -3775,7 +3879,7 @@
                             stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED,
                                     UserHandle.USER_SYSTEM, "android");
                         }
-                        mSettings.writeLPr();
+                        writeSettingsLPrTEMP();
                     }
                 }
                 return false;
@@ -5789,10 +5893,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<>();
@@ -5837,6 +5937,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);
                 }
             }
@@ -7819,7 +7924,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;
@@ -8946,10 +9051,10 @@
         if (providerInfo == null) {
             return null;
         }
-        if (!mSettings.isEnabledAndMatchLPr(providerInfo, flags, userId)) {
-            return null;
-        }
         synchronized (mLock) {
+            if (!mSettings.isEnabledAndMatchLPr(providerInfo, flags, userId)) {
+                return null;
+            }
             final PackageSetting ps = mSettings.mPackages.get(providerInfo.packageName);
             final ComponentName component =
                     new ComponentName(providerInfo.packageName, providerInfo.name);
@@ -9036,9 +9141,11 @@
             String targetPackage, int flags) {
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
-        final PackageSetting ps = mSettings.mPackages.get(targetPackage);
-        if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
-            return ParceledListSlice.emptyList();
+        synchronized (mLock) {
+            final PackageSetting ps = mSettings.getPackageLPr(targetPackage);
+            if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
+                return ParceledListSlice.emptyList();
+            }
         }
         return new ParceledListSlice<>(queryInstrumentationInternal(targetPackage, flags,
                 callingUserId));
@@ -11569,7 +11676,7 @@
             configurePackageComponents(parsedPackage);
         }
 
-        final String cpuAbiOverride = deriveAbiOverride(request.cpuAbiOverride, pkgSetting);
+        final String cpuAbiOverride = deriveAbiOverride(request.cpuAbiOverride);
         final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
 
         if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
@@ -11663,8 +11770,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
@@ -11672,9 +11780,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) {
@@ -13075,7 +13184,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;
@@ -13773,7 +13882,7 @@
         final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
         final long callingId = Binder.clearCallingIdentity();
         try {
-            final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
+            final String activeLauncherPackageName = mPermissionManager.getDefaultHome(userId);
             final String dialerPackageName = mPermissionManager.getDefaultDialer(userId);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
@@ -13849,18 +13958,6 @@
         return canSuspend;
     }
 
-    private String getActiveLauncherPackageName(int userId) {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_HOME);
-        ResolveInfo resolveInfo = resolveIntent(
-                intent,
-                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                PackageManager.MATCH_DEFAULT_ONLY,
-                userId);
-
-        return resolveInfo == null ? null : resolveInfo.activityInfo.packageName;
-    }
-
     @Override
     public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
         mContext.enforceCallingOrSelfPermission(
@@ -14552,7 +14649,7 @@
         final PackageSetting ps;
         int appId = -1;
         long ceDataInode = -1;
-        synchronized (mSettings) {
+        synchronized (mLock) {
             ps = mSettings.getPackageLPr(packageName);
             if (ps != null) {
                 appId = ps.appId;
@@ -15181,6 +15278,13 @@
         }
 
         public void handleStartCopy() {
+            if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
+                // Apex packages get verified in StagingManager currently.
+                // TODO(b/136257624): Move apex verification logic out of StagingManager
+                mRet = INSTALL_SUCCEEDED;
+                return;
+            }
+
             PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                     origin.resolvedPath, installFlags, packageAbiOverride);
 
@@ -16263,7 +16367,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);
         }
 
@@ -17279,7 +17383,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);
@@ -17423,7 +17527,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;
@@ -17593,12 +17697,13 @@
                 }
                 boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
                         && pkgSetting.getPkgState().isUpdatedSystemApp();
+                final String abiOverride = deriveAbiOverride(args.abiOverride);
                 AndroidPackage oldPackage = mPackages.get(pkgName);
                 boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
                         derivedAbi = mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
                         isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        args.abiOverride);
+                        abiOverride);
                 derivedAbi.first.applyTo(parsedPackage);
                 derivedAbi.second.applyTo(parsedPackage);
             } catch (PackageManagerException pme) {
@@ -18860,6 +18965,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
@@ -18869,10 +18978,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) {
@@ -18916,7 +19025,7 @@
             // can downgrade to reader
             if (writeSettings) {
                 // Save settings now
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
             if (installedStateChanged) {
                 mSettings.writeKernelMappingLPr(deletedPs);
@@ -19003,8 +19112,7 @@
         if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         try {
             installPackageFromSystemLIF(disabledPs.getCodePathString(), allUserHandles,
-                    outInfo == null ? null : outInfo.origUsers, deletedPs.getPermissionsState(),
-                    writeSettings);
+                    outInfo == null ? null : outInfo.origUsers, writeSettings);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.getPackageName() + ": "
                     + e.getMessage());
@@ -19035,9 +19143,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
@@ -19074,12 +19181,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
@@ -19113,7 +19216,7 @@
             }
             // can downgrade to reader here
             if (writeSettings) {
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
         return pkg;
@@ -19187,7 +19290,7 @@
             } else {
                 ps.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
             }
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
         }
         return true;
     }
@@ -19427,6 +19530,9 @@
         }
 
         if (outInfo != null) {
+            if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+                outInfo.dataRemoved = true;
+            }
             outInfo.removedPackage = ps.name;
             outInfo.installerPackageName = ps.installSource.installerPackageName;
             outInfo.isStaticSharedLib = pkg != null && pkg.getStaticSharedLibName() != null;
@@ -19462,9 +19568,11 @@
         mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "clear application data");
 
-        final PackageSetting ps = mSettings.getPackageLPr(packageName);
-        final boolean filterApp =
-                (ps != null && shouldFilterApplicationLocked(ps, callingUid, userId));
+        final boolean filterApp;
+        synchronized (mLock) {
+            final PackageSetting ps = mSettings.getPackageLPr(packageName);
+            filterApp = shouldFilterApplicationLocked(ps, callingUid, userId);
+        }
         if (!filterApp && mProtectedPackages.isPackageDataProtected(userId, packageName)) {
             throw new SecurityException("Cannot clear data for a protected package: "
                     + packageName);
@@ -19744,11 +19852,13 @@
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
                 != PackageManager.PERMISSION_GRANTED) {
-            if (getUidTargetSdkVersionLockedLPr(callingUid)
-                    < Build.VERSION_CODES.FROYO) {
-                Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
-                        + callingUid);
-                return;
+            synchronized (mLock) {
+                if (getUidTargetSdkVersionLockedLPr(callingUid)
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
+                            + callingUid);
+                    return;
+                }
             }
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
@@ -19928,8 +20038,9 @@
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     private void clearPackagePreferredActivities(String packageName, int userId) {
         final SparseBooleanArray changedUsers = new SparseBooleanArray();
-
-        clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+        synchronized (mLock) {
+            clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+        }
         if (changedUsers.size() > 0) {
             updateDefaultHomeNotLocked(changedUsers);
             postPreferredActivityChangedBroadcast(userId);
@@ -20051,7 +20162,9 @@
         // writer
         try {
             final SparseBooleanArray changedUsers = new SparseBooleanArray();
-            clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+            synchronized (mLock) {
+                clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+            }
             if (changedUsers.size() > 0) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -20379,7 +20492,7 @@
                     (parser1, userId1) -> {
                         synchronized (mLock) {
                             mSettings.readAllDomainVerificationsLPr(parser1, userId1);
-                            mSettings.writeLPr();
+                            writeSettingsLPrTEMP();
                         }
                     });
         } catch (Exception e) {
@@ -20519,6 +20632,9 @@
         if (cn != null) {
             return cn;
         }
+        // TODO: This should not happen since there should always be a default package set for
+        //  ROLE_HOME in RoleManager. Continue with a warning log for now.
+        Slog.w(TAG, "Default package for ROLE_HOME is not set in RoleManager");
 
         // Find the launcher with the highest priority and return that component if there are no
         // other home activity with the same priority.
@@ -20567,6 +20683,7 @@
         if (packageName == null) {
             return null;
         }
+
         int resolveInfosSize = resolveInfos.size();
         for (int i = 0; i < resolveInfosSize; i++) {
             ResolveInfo resolveInfo = resolveInfos.get(i);
@@ -20626,6 +20743,11 @@
             // PermissionController manages default home directly.
             return false;
         }
+
+        if (packageName == null) {
+            // Keep the default home package in RoleManager.
+            return false;
+        }
         mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
             if (successful) {
                 postPreferredActivityChangedBroadcast(userId);
@@ -21061,15 +21183,19 @@
         // Limit who can change which apps
         if (!UserHandle.isSameApp(callingUid, pkgSetting.appId)) {
             // Don't allow apps that don't have permission to modify other apps
-            if (!allowedByPermission
-                    || shouldFilterApplicationLocked(pkgSetting, callingUid, userId)) {
+            final boolean filterApp;
+            synchronized (mLock) {
+                filterApp = (!allowedByPermission
+                        || shouldFilterApplicationLocked(pkgSetting, callingUid, userId));
+            }
+            if (filterApp) {
                 throw new SecurityException(
                         "Attempt to change component state; "
-                        + "pid=" + Binder.getCallingPid()
-                        + ", uid=" + callingUid
-                        + (className == null
+                                + "pid=" + Binder.getCallingPid()
+                                + ", uid=" + callingUid
+                                + (className == null
                                 ? ", package=" + packageName
-                                : ", component=" + packageName + "/" + className));
+                                        : ", component=" + packageName + "/" + className));
             }
             // Don't allow changing protected packages.
             if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
@@ -21558,7 +21684,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++) {
@@ -21730,6 +21856,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;
@@ -21925,7 +22053,7 @@
                 dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
             } else if ("write".equals(cmd)) {
                 synchronized (mLock) {
-                    mSettings.writeLPr();
+                    writeSettingsLPrTEMP();
                     pw.println("Settings written.");
                     return;
                 }
@@ -22643,7 +22771,7 @@
             // Yay, everything is now upgraded
             ver.forceCurrent();
 
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
         }
 
         for (PackageFreezer freezer : freezers) {
@@ -22693,7 +22821,7 @@
                     AttributeCache.instance().removePackage(ps.name);
                 }
 
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
 
@@ -22743,6 +22871,14 @@
         }
     }
 
+    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
@@ -22882,6 +23018,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);
@@ -22902,10 +23040,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;
@@ -22930,6 +23070,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*/)) {
@@ -22944,16 +23085,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);
     }
 
     /**
@@ -22964,26 +23109,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));
@@ -23003,57 +23155,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,
@@ -23572,6 +23734,8 @@
         synchronized (mLock) {
             mDirtyUsers.remove(userId);
             mUserNeedsBadging.delete(userId);
+            mPermissionManager.onUserRemoved(userId);
+            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
             mInstantAppRegistry.onUserRemovedLPw(userId);
@@ -23672,7 +23836,9 @@
 
     boolean readPermissionStateForUser(@UserIdInt int userId) {
         synchronized (mPackages) {
+            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
             mSettings.readPermissionStateForUserSyncLPr(userId);
+            mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
             return mPmInternal.isPermissionUpgradeNeeded(userId);
         }
     }
@@ -24545,12 +24711,6 @@
         @Override
         public int getPackageUid(String packageName, int flags, int userId) {
             return PackageManagerService.this
-                    .getPackageUid(packageName, flags, userId);
-        }
-
-        @Override
-        public int getPackageUidInternal(String packageName, int flags, int userId) {
-            return PackageManagerService.this
                     .getPackageUidInternal(packageName, flags, userId, Process.SYSTEM_UID);
         }
 
@@ -25128,7 +25288,7 @@
                 if (async) {
                     scheduleWriteSettingsLocked();
                 } else {
-                    mSettings.writeLPr();
+                    writeSettingsLPrTEMP();
                 }
             }
         }
@@ -25175,7 +25335,7 @@
                     return;
                 }
                 mSettings.mReadExternalStorageEnforced = enforced ? Boolean.TRUE : Boolean.FALSE;
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
 
@@ -25689,6 +25849,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 {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index de0e4b5..5553cd0 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;
 
@@ -423,18 +421,13 @@
 
     /**
      * Derive the value of the {@code cpuAbiOverride} based on the provided
-     * value and an optional stored value from the package settings.
+     * value.
      */
-    public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
-        String cpuAbiOverride = null;
+    public static String deriveAbiOverride(String abiOverride) {
         if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
-            cpuAbiOverride = null;
-        } else if (abiOverride != null) {
-            cpuAbiOverride = abiOverride;
-        } else if (settings != null) {
-            cpuAbiOverride = settings.cpuAbiOverrideString;
+            return null;
         }
-        return cpuAbiOverride;
+        return abiOverride;
     }
 
     /**
@@ -968,20 +961,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 7aeec6d..4d2e22a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -103,6 +103,7 @@
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
@@ -138,7 +139,7 @@
     private static final String STDIN_PATH = "-";
     /** Path where ART profiles snapshots are dumped for the shell user */
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
-    private static final int DEFAULT_WAIT_MS = 60 * 1000;
+    private static final int DEFAULT_STAGED_READY_TIMEOUT_MS = 60 * 1000;
     private static final String TAG = "PackageManagerShellCommand";
 
     final IPackageManager mInterface;
@@ -463,9 +464,20 @@
         return 1;
     }
 
-    private int runRollbackApp() {
+    private int runRollbackApp() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
 
+        String opt;
+        long stagedReadyTimeoutMs = DEFAULT_STAGED_READY_TIMEOUT_MS;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--staged-ready-timeout":
+                    stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
         final String packageName = getNextArgRequired();
         if (packageName == null) {
             pw.println("Error: package name not specified");
@@ -495,14 +507,21 @@
         final Intent result = receiver.getResult();
         final int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                 RollbackManager.STATUS_FAILURE);
-        if (status == RollbackManager.STATUS_SUCCESS) {
-            pw.println("Success");
-            return 0;
-        } else {
+
+        if (status != RollbackManager.STATUS_SUCCESS) {
             pw.println("Failure ["
                     + result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE) + "]");
             return 1;
         }
+
+        if (rollback.isStaged() && stagedReadyTimeoutMs > 0) {
+            final int committedSessionId = rollback.getCommittedSessionId();
+            return doWaitForStagedSessionReady(committedSessionId, stagedReadyTimeoutMs, pw);
+        }
+
+        pw.println("Success");
+        return 0;
+
     }
 
     private void setParamsSize(InstallParams params, List<String> inPaths) {
@@ -1043,7 +1062,9 @@
                 + "; isStaged = " + session.isStaged()
                 + "; isReady = " + session.isStagedSessionReady()
                 + "; isApplied = " + session.isStagedSessionApplied()
-                + "; isFailed = " + session.isStagedSessionFailed() + ";");
+                + "; isFailed = " + session.isStagedSessionFailed()
+                + "; errorMsg = " + session.getStagedSessionErrorMessage()
+                + ";");
     }
 
     private Intent parseIntentAndUser() throws URISyntaxException {
@@ -1305,11 +1326,12 @@
             }
             abandonSession = false;
 
-            if (!params.sessionParams.isStaged || !params.mWaitForStagedSessionReady) {
-                pw.println("Success");
-                return 0;
+            if (params.sessionParams.isStaged && params.stagedReadyTimeoutMs > 0) {
+                return doWaitForStagedSessionReady(sessionId, params.stagedReadyTimeoutMs, pw);
             }
-            return doWaitForStagedSessionRead(sessionId, params.timeoutMs, pw);
+
+            pw.println("Success");
+            return 0;
         } finally {
             if (abandonSession) {
                 try {
@@ -1320,11 +1342,9 @@
         }
     }
 
-    private int doWaitForStagedSessionRead(int sessionId, long timeoutMs, PrintWriter pw)
+    private int doWaitForStagedSessionReady(int sessionId, long timeoutMs, PrintWriter pw)
               throws RemoteException {
-        if (timeoutMs <= 0) {
-            timeoutMs = DEFAULT_WAIT_MS;
-        }
+        Preconditions.checkArgument(timeoutMs > 0);
         PackageInstaller.SessionInfo si = mInterface.getPackageInstaller()
                 .getSessionInfo(sessionId);
         if (si == null) {
@@ -1374,25 +1394,14 @@
     private int runInstallCommit() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         String opt;
-        boolean waitForStagedSessionReady = true;
-        long timeoutMs = -1;
+        long stagedReadyTimeoutMs = DEFAULT_STAGED_READY_TIMEOUT_MS;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
-                case "--wait-for-staged-ready":
-                    waitForStagedSessionReady = true;
-                    // If there is only one remaining argument, then it represents the sessionId, we
-                    // shouldn't try to parse it as timeoutMs.
-                    if (getRemainingArgsCount() > 1) {
-                        try {
-                            timeoutMs = Long.parseLong(peekNextArg());
-                            getNextArg();
-                        } catch (NumberFormatException ignore) {
-                        }
-                    }
+                case "--staged-ready-timeout":
+                    stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
                     break;
-                case "--no-wait":
-                    waitForStagedSessionReady = false;
-                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown option: " + opt);
             }
         }
         final int sessionId = Integer.parseInt(getNextArg());
@@ -1401,11 +1410,11 @@
         }
         final PackageInstaller.SessionInfo si = mInterface.getPackageInstaller()
                 .getSessionInfo(sessionId);
-        if (si == null || !si.isStaged() || !waitForStagedSessionReady) {
-            pw.println("Success");
-            return 0;
+        if (si != null && si.isStaged() && stagedReadyTimeoutMs > 0) {
+            return doWaitForStagedSessionReady(sessionId, stagedReadyTimeoutMs, pw);
         }
-        return doWaitForStagedSessionRead(sessionId, timeoutMs, pw);
+        pw.println("Success");
+        return 0;
     }
 
     private int runInstallCreate() throws RemoteException {
@@ -2736,8 +2745,7 @@
         SessionParams sessionParams;
         String installerPackageName;
         int userId = UserHandle.USER_ALL;
-        boolean mWaitForStagedSessionReady = true;
-        long timeoutMs = DEFAULT_WAIT_MS;
+        long stagedReadyTimeoutMs = DEFAULT_STAGED_READY_TIMEOUT_MS;
     }
 
     private InstallParams makeInstallParams() {
@@ -2866,16 +2874,8 @@
                     }
                     sessionParams.installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
                     break;
-                case "--wait-for-staged-ready":
-                    params.mWaitForStagedSessionReady = true;
-                    try {
-                        params.timeoutMs = Long.parseLong(peekNextArg());
-                        getNextArg();
-                    } catch (NumberFormatException ignore) {
-                    }
-                    break;
-                case "--no-wait":
-                    params.mWaitForStagedSessionReady = false;
+                case "--staged-ready-timeout":
+                    params.stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
                     break;
                 case "--skip-verification":
                     sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
@@ -3197,12 +3197,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;
@@ -3338,7 +3350,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());
@@ -3599,7 +3611,7 @@
         pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
         pw.println("       [--enable-rollback]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
-        pw.println("       [--apex] [--wait-for-staged-ready TIMEOUT]");
+        pw.println("       [--apex] [--staged-ready-timeout TIMEOUT]");
         pw.println("       [PATH [SPLIT...]|-]");
         pw.println("    Install an application.  Must provide the apk data to install, either as");
         pw.println("    file path(s) or '-' to read from stdin.  Options are:");
@@ -3627,9 +3639,11 @@
         pw.println("          3=device setup, 4=user request");
         pw.println("      --force-uuid: force install on to disk volume with given UUID");
         pw.println("      --apex: install an .apex file, not an .apk");
-        pw.println("      --wait-for-staged-ready: when performing staged install, wait TIMEOUT");
-        pw.println("          ms for pre-reboot verification to complete. If TIMEOUT is not");
-        pw.println("          specified it will wait for " + DEFAULT_WAIT_MS + " milliseconds.");
+        pw.println("      --staged-ready-timeout: By default, staged sessions wait "
+                + DEFAULT_STAGED_READY_TIMEOUT_MS);
+        pw.println("          milliseconds for pre-reboot verification to complete when");
+        pw.println("          performing staged install. This flag is used to alter the waiting");
+        pw.println("          time. You can skip the waiting time by specifying a TIMEOUT of '0'");
         pw.println("");
         pw.println("  install-existing [--user USER_ID|all|current]");
         pw.println("       [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e25367..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;
@@ -954,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) {
@@ -4148,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;
                 }
@@ -4187,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);
         }
@@ -4652,6 +4551,7 @@
                     ? "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) {
@@ -5547,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 700f7be..8412077 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3301,7 +3301,7 @@
 
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    int packageUid = mPackageManagerInternal.getPackageUidInternal(packageName,
+                    int packageUid = mPackageManagerInternal.getPackageUid(packageName,
                             PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
                     // Grant read uri permission to the caller on behalf of the shortcut owner. All
                     // granted permissions are revoked when the default launcher changes, or when
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index ac05aab..0c4eaec3 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -88,7 +88,6 @@
 import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -189,7 +188,6 @@
      * Validates the signature used to sign the container of the new apex package
      *
      * @param newApexPkg The new apex package that is being installed
-     * @throws PackageManagerException
      */
     private void validateApexSignature(PackageInfo newApexPkg)
             throws PackageManagerException {
@@ -591,13 +589,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) {
@@ -702,6 +701,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()) {
@@ -714,12 +723,9 @@
         return ret;
     }
 
-    @NonNull
     private PackageInstallerSession createAndWriteApkSession(
-            @NonNull PackageInstallerSession originalSession, boolean preReboot)
-            throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
+            PackageInstallerSession originalSession) throws PackageManagerException {
+        final int errorCode = SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
         if (originalSession.stageDir == null) {
             Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
             throw new PackageManagerException(errorCode,
@@ -735,12 +741,7 @@
         PackageInstaller.SessionParams params = originalSession.params.copy();
         params.isStaged = false;
         params.installFlags |= PackageManager.INSTALL_STAGED;
-        if (preReboot) {
-            params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
-            params.installFlags |= PackageManager.INSTALL_DRY_RUN;
-        } else {
-            params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
-        }
+        params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
         try {
             int apkSessionId = mPi.createSession(
                     params, originalSession.getInstallerPackageName(),
@@ -772,12 +773,10 @@
      * apks in the given session. Only parent session is returned for multi-package session.
      */
     @Nullable
-    private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
-            boolean preReboot) throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
+    private PackageInstallerSession extractApksInSession(PackageInstallerSession session)
+            throws PackageManagerException {
         if (!session.isMultiPackage() && !isApexSession(session)) {
-            return createAndWriteApkSession(session, preReboot);
+            return createAndWriteApkSession(session);
         } else if (session.isMultiPackage()) {
             // For multi-package staged sessions containing APKs, we identify which child sessions
             // contain an APK, and with those then create a new multi-package group of sessions,
@@ -799,10 +798,6 @@
             }
             final PackageInstaller.SessionParams params = session.params.copy();
             params.isStaged = false;
-            if (preReboot) {
-                params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
-                params.installFlags |= PackageManager.INSTALL_DRY_RUN;
-            }
             final int apkParentSessionId = mPi.createSession(
                     params, session.getInstallerPackageName(), session.getInstallerAttributionTag(),
                     session.userId);
@@ -812,18 +807,18 @@
             } catch (IOException e) {
                 Slog.e(TAG, "Unable to prepare multi-package session for staged session "
                         + session.sessionId);
-                throw new PackageManagerException(errorCode,
+                throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                         "Unable to prepare multi-package session for staged session");
             }
 
             for (int i = 0, size = childSessions.size(); i < size; i++) {
                 final PackageInstallerSession apkChildSession = createAndWriteApkSession(
-                        childSessions.get(i), preReboot);
+                        childSessions.get(i));
                 try {
                     apkParentSession.addChildSessionId(apkChildSession.sessionId);
                 } catch (IllegalStateException e) {
                     Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
-                    throw new PackageManagerException(errorCode,
+                    throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                             "Failed to add a child session " + apkChildSession.sessionId);
                 }
             }
@@ -866,11 +861,9 @@
         }
     }
 
-    private void installApksInSession(@NonNull PackageInstallerSession session)
+    private void installApksInSession(PackageInstallerSession session)
             throws PackageManagerException {
-
-        final PackageInstallerSession apksToInstall = extractApksInSession(
-                session, /* preReboot */ false);
+        final PackageInstallerSession apksToInstall = extractApksInSession(session);
         if (apksToInstall == null) {
             return;
         }
@@ -994,7 +987,7 @@
                             // will be deleted.
                         }
                         root.setStagedSessionFailed(
-                                SessionInfo.STAGED_SESSION_OTHER_ERROR,
+                                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);
@@ -1036,7 +1029,7 @@
     /**
      * <p>Abort committed staged session
      *
-     * <p>This method must be called while holding {@link PackageInstallerSession.mLock}.
+     * <p>This method must be called while holding {@link PackageInstallerSession#mLock}.
      *
      * <p>The method returns {@code false} to indicate it is not safe to clean up the session from
      * system yet. When it is safe, the method returns {@code true}.
@@ -1207,7 +1200,7 @@
         }
     }
 
-    void markStagedSessionsAsSuccessful() {
+    private void markStagedSessionsAsSuccessful() {
         synchronized (mSuccessfulStagedSessionIds) {
             for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) {
                 mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i));
@@ -1231,30 +1224,10 @@
         mFailureReasonFile.delete();
     }
 
-    private static class LocalIntentReceiverAsync {
-        final Consumer<Intent> mConsumer;
-
-        LocalIntentReceiverAsync(Consumer<Intent> consumer) {
-            mConsumer = consumer;
-        }
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                mConsumer.accept(intent);
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-    }
-
     private static class LocalIntentReceiverSync {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+        private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                     IIntentReceiver finishedReceiver, String requiredPermission,
@@ -1288,6 +1261,20 @@
         return session;
     }
 
+    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
+    //  verification logic is extracted out of StagingManager into PMS, we can remove
+    //  this.
+    void notifyVerificationComplete(int sessionId) {
+        mPreRebootVerificationHandler.onPreRebootVerificationComplete(sessionId);
+    }
+
+    // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all
+    //  verification logic is extracted out of StagingManager into PMS, we can remove
+    //  this.
+    void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(sessionId);
+    }
+
     private final class PreRebootVerificationHandler extends Handler {
         // Hold session ids before handler gets ready to do the verification.
         private IntArray mPendingSessionIds;
@@ -1489,54 +1476,16 @@
         }
 
         /**
-         * Pre-reboot verification state for apk files:
-         *   <p><ul>
-         *       <li>performs a dry-run install of apk</li>
-         *   </ul></p>
+         * Pre-reboot verification state for apk files. Session is sent to
+         * {@link PackageManagerService} for verification and it notifies back the result via
+         * {@link #notifyPreRebootVerification_Apk_Complete(int)}
          */
         private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
             if (!sessionContainsApk(session)) {
                 notifyPreRebootVerification_Apk_Complete(session.sessionId);
                 return;
             }
-
-            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);
-            } catch (PackageManagerException e) {
-                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);
+            session.verifyStagedSession();
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d137fd0..e44c8ab 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -760,6 +760,8 @@
         return null;
     }
 
+    // TODO(b/157921703): replace by getAliveUsers() or remove (so callers
+    // explicitly call the 3-booleans version)
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
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 3d15704..f5dd918 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -162,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;
@@ -227,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;
 
@@ -671,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;
@@ -684,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);
     }
 
@@ -771,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;
         }
@@ -789,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) {
@@ -864,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());
@@ -923,12 +928,11 @@
         }
 
         final int uid = UserHandle.getUid(userId, pkg.getUid());
-        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;
@@ -1139,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;
             }
 
@@ -1451,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
@@ -1464,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 "
@@ -1599,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;
         }
@@ -1613,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
@@ -1624,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
@@ -2456,15 +2466,58 @@
         }
     }
 
+    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 null;
+            return Collections.emptySet();
         }
-        final PermissionsState permissionsState = ps.getPermissionsState();
-        return permissionsState.getPermissions(userId);
+        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
@@ -2482,7 +2535,11 @@
         if (ps == null) {
             return null;
         }
-        final PermissionsState permissionsState = ps.getPermissionsState();
+        final PermissionsState permissionsState = getPermissionsState(ps);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName);
+            return null;
+        }
         return permissionsState.computeGids(userId);
     }
 
@@ -2520,8 +2577,7 @@
         if (ps == null) {
             return;
         }
-
-        final PermissionsState permissionsState = ps.getPermissionsState();
+        final PermissionsState permissionsState = getOrCreatePermissionsState(ps);
 
         final int[] userIds = getAllUserIds();
 
@@ -2593,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(),
-                            userIds);
+                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+                            ps.getSharedUser().getPackages(), permissionsState, userIds);
                     if (!ArrayUtils.isEmpty(updatedUserIds)) {
                         runtimePermissionsRevoked = true;
                     }
@@ -3070,6 +3126,8 @@
                     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.
@@ -3533,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'
@@ -3714,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;
     }
@@ -3736,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);
     }
 
@@ -3766,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;
 
@@ -3817,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;
         }
 
@@ -3937,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;
@@ -3949,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;
         }
@@ -3972,7 +4095,6 @@
             }
         }
 
-        PermissionsState permissionsState = suSetting.getPermissionsState();
         // Prune install permissions
         List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
         final int installPermCount = installPermStates.size();
@@ -4258,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,
@@ -4427,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) {
@@ -4444,7 +4565,7 @@
             return;
         }
         String errorMessage = buildInvalidCrossUserPermissionMessage(
-                message, requireFullPermission);
+                callingUid, userId, message, requireFullPermission);
         Slog.w(TAG, errorMessage);
         throw new SecurityException(errorMessage);
     }
@@ -4463,7 +4584,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 enforceCrossUserOrProfilePermission(int callingUid, int userId,
+    private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             String message) {
         if (userId < 0) {
@@ -4489,7 +4610,7 @@
             return;
         }
         String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
-                message, requireFullPermission, isSameProfileGroup);
+                callingUid, userId, message, requireFullPermission, isSameProfileGroup);
         Slog.w(TAG, errorMessage);
         throw new SecurityException(errorMessage);
     }
@@ -4524,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();
     }
@@ -4670,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() {
@@ -4701,6 +4887,29 @@
         public void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
             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,
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 cfa371d..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,6 +24,7 @@
 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;
@@ -265,6 +266,52 @@
     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
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 548cd70..d01a30f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -69,6 +69,7 @@
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -148,6 +149,7 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
@@ -1378,12 +1380,14 @@
     }
 
     private long getScreenshotChordLongPressDelay() {
+        long delayMs = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_SYSTEMUI, SCREENSHOT_KEYCHORD_DELAY,
+                ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout());
         if (mKeyguardDelegate.isShowing()) {
             // Double the time it takes to take a screenshot from the keyguard
-            return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER *
-                    ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout());
+            return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * delayMs);
         }
-        return ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout();
+        return delayMs;
     }
 
     private long getRingerToggleChordDelay() {
@@ -2219,11 +2223,6 @@
     }
 
     @Override
-    public int getMaxWallpaperLayer() {
-        return getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
-    }
-
-    @Override
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
@@ -5324,15 +5323,6 @@
     }
 
     @Override
-    public boolean isTopLevelWindow(int windowType) {
-        if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
-                && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
-            return (windowType == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG);
-        }
-        return true;
-    }
-
-    @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(ROTATION_MODE, mDefaultDisplayRotation.getUserRotationMode());
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 651eafd..b96d65c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -67,7 +67,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -90,7 +89,6 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
 import com.android.server.wm.DisplayRotation;
-import com.android.server.wm.WindowFrames;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -181,92 +179,11 @@
      */
     public interface WindowState {
         /**
-         * Return the uid of the app that owns this window.
-         */
-        int getOwningUid();
-
-        /**
          * Return the package name of the app that owns this window.
          */
         String getOwningPackage();
 
         /**
-         * Perform standard frame computation.  The result can be obtained with
-         * getFrame() if so desired.  Must be called with the window manager
-         * lock held.
-         *
-         */
-        public void computeFrameLw();
-
-        /**
-         * Retrieve the current frame of the window that has been assigned by
-         * the window manager.  Must be called with the window manager lock held.
-         *
-         * @return Rect The rectangle holding the window frame.
-         */
-        public Rect getFrameLw();
-
-        /**
-         * Retrieve the frame of the display that this window was last
-         * laid out in.  Must be called with the
-         * window manager lock held.
-         *
-         * @return Rect The rectangle holding the display frame.
-         */
-        public Rect getDisplayFrameLw();
-
-        /**
-         * Retrieve the frame of the content area that this window was last
-         * laid out in.  This is the area in which the content of the window
-         * should be placed.  It will be smaller than the display frame to
-         * account for screen decorations such as a status bar or soft
-         * keyboard.  Must be called with the
-         * window manager lock held.
-         *
-         * @return Rect The rectangle holding the content frame.
-         */
-        public Rect getContentFrameLw();
-
-        /**
-         * Retrieve the frame of the visible area that this window was last
-         * laid out in.  This is the area of the screen in which the window
-         * will actually be fully visible.  It will be smaller than the
-         * content frame to account for transient UI elements blocking it
-         * such as an input method's candidates UI.  Must be called with the
-         * window manager lock held.
-         *
-         * @return Rect The rectangle holding the visible frame.
-         */
-        public Rect getVisibleFrameLw();
-
-        /**
-         * Returns true if this window is waiting to receive its given
-         * internal insets from the client app, and so should not impact the
-         * layout of other windows.
-         */
-        public boolean getGivenInsetsPendingLw();
-
-        /**
-         * Retrieve the insets given by this window's client for the content
-         * area of windows behind it.  Must be called with the
-         * window manager lock held.
-         *
-         * @return Rect The left, top, right, and bottom insets, relative
-         * to the window's frame, of the actual contents.
-         */
-        public Rect getGivenContentInsetsLw();
-
-        /**
-         * Retrieve the insets given by this window's client for the visible
-         * area of windows behind it.  Must be called with the
-         * window manager lock held.
-         *
-         * @return Rect The left, top, right, and bottom insets, relative
-         * to the window's frame, of the actual visible area.
-         */
-        public Rect getGivenVisibleInsetsLw();
-
-        /**
          * Retrieve the current LayoutParams of the window.
          *
          * @return WindowManager.LayoutParams The window's internal LayoutParams
@@ -275,17 +192,6 @@
         public WindowManager.LayoutParams getAttrs();
 
         /**
-         * Retrieve the current system UI visibility flags associated with
-         * this window.
-         */
-        public int getSystemUiVisibility();
-
-        /**
-         * Get the layer at which this window's surface will be Z-ordered.
-         */
-        public int getSurfaceLayer();
-
-        /**
          * Retrieve the type of the top-level window.
          *
          * @return the base type of the parent window if attached or its own type otherwise
@@ -301,22 +207,6 @@
         public IApplicationToken getAppToken();
 
         /**
-         * Return true if this window is participating in voice interaction.
-         */
-        public boolean isVoiceInteraction();
-
-        /**
-         * Return true if, at any point, the application token associated with
-         * this window has actually displayed any windows.  This is most useful
-         * with the "starting up" window to determine if any windows were
-         * displayed when it is closed.
-         *
-         * @return Returns true if one or more windows have been displayed,
-         *         else false.
-         */
-        public boolean hasAppShownWindows();
-
-        /**
          * Is this window visible?  It is not visible if there is no
          * surface, or we are in the process of running an exit animation
          * that will remove the surface.
@@ -324,42 +214,12 @@
         boolean isVisibleLw();
 
         /**
-         * Is this window currently visible to the user on-screen?  It is
-         * displayed either if it is visible or it is currently running an
-         * animation before no longer being visible.  Must be called with the
-         * window manager lock held.
-         */
-        boolean isDisplayedLw();
-
-        /**
          * Return true if this window (or a window it is attached to, but not
          * considering its app token) is currently animating.
          */
         boolean isAnimatingLw();
 
         /**
-         * Is this window considered to be gone for purposes of layout?
-         */
-        boolean isGoneForLayoutLw();
-
-        /**
-         * Returns true if the window has a surface that it has drawn a
-         * complete UI in to. Note that this is different from {@link #hasDrawnLw()}
-         * in that it also returns true if the window is READY_TO_SHOW, but was not yet
-         * promoted to HAS_DRAWN.
-         */
-        boolean isDrawnLw();
-
-        /**
-         * Returns true if this window has been shown on screen at some time in
-         * the past.  Must be called with the window manager lock held.
-         *
-         * @deprecated Use {@link #isDrawnLw} or any of the other drawn/visibility methods.
-         */
-        @Deprecated
-        public boolean hasDrawnLw();
-
-        /**
          * Can be called by the policy to force a window to be hidden,
          * regardless of whether the client or window manager would like
          * it shown.  Must be called with the window manager lock held.
@@ -377,51 +237,12 @@
         public boolean showLw(boolean doAnimation);
 
         /**
-         * Check whether the process hosting this window is currently alive.
-         */
-        public boolean isAlive();
-
-        /**
-         * Check if window is on {@link Display#DEFAULT_DISPLAY}.
-         * @return true if window is on default display.
-         */
-        public boolean isDefaultDisplay();
-
-        /**
          * Check whether the window is currently dimming.
          */
         public boolean isDimming();
 
-        /**
-         * Returns true if the window is letterboxed for the display cutout.
-         */
-        default boolean isLetterboxedForDisplayCutoutLw() {
-            return false;
-        }
-
-        /** @return the current windowing mode of this window. */
-        int getWindowingMode();
-
-        /**
-         * Returns the {@link WindowConfiguration.ActivityType} associated with the configuration
-         * of this window.
-         */
-        default int getActivityType() {
-            return WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-        }
-
-        /**
-         * Returns true if the window is current in multi-windowing mode. i.e. it shares the
-         * screen with other application windows.
-         */
-        boolean inMultiWindowMode();
-
-        public int getRotationAnimationHint();
-
         public boolean isInputMethodWindow();
 
-        public boolean isInputMethodTarget();
-
         public int getDisplayId();
 
         /**
@@ -432,42 +253,8 @@
             return false;
         }
 
-        /**
-         * Returns true if the window owner has the permission to acquire a sleep token when it's
-         * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
-         */
-        boolean canAcquireSleepToken();
-
-        /** @return true if this window desires key events. */
-        boolean canReceiveKeys();
-
         /** @return true if the window can show over keyguard. */
         boolean canShowWhenLocked();
-
-        /**
-         * Writes {@link com.android.server.wm.IdentifierProto} to stream.
-         */
-        void writeIdentifierToProto(ProtoOutputStream proto, long fieldId);
-
-        /**
-         * @return The {@link WindowFrames} associated with this {@link WindowState}
-         */
-        WindowFrames getWindowFrames();
-    }
-
-    /**
-     * Representation of a input consumer that the policy has added to the
-     * window manager to consume input events going to windows below it.
-     */
-    public interface InputConsumer {
-        /**
-         * Remove the input consumer from the window manager.
-         */
-        void dismiss();
-        /**
-         * Dispose the input consumer and input receiver from UI thread.
-         */
-        void dispose();
     }
 
     /**
@@ -538,11 +325,6 @@
         void unregisterPointerEventListener(PointerEventListener listener, int displayId);
 
         /**
-         * @return The currently active input method window.
-         */
-        WindowState getInputMethodWindowLw();
-
-        /**
          * Notifies window manager that {@link #isKeyguardTrustedLw} has changed.
          */
         void notifyKeyguardTrustedChanged();
@@ -615,17 +397,6 @@
     }
 
     /**
-     * Provides the rotation of a device.
-     *
-     * @see com.android.server.policy.WindowOrientationListener
-     */
-    public interface RotationSource {
-        int getProposedRotation();
-
-        void setCurrentRotation(int rotation);
-    }
-
-    /**
      * Interface to get public information of a display content.
      */
     public interface DisplayContentInfo {
@@ -889,12 +660,6 @@
     }
 
     /**
-     * Get the highest layer (actually one more than) that the wallpaper is
-     * allowed to be in.
-     */
-    public int getMaxWallpaperLayer();
-
-    /**
      * Return whether the given window can become the Keyguard window. Typically returns true for
      * the StatusBar.
      */
@@ -1384,17 +1149,6 @@
     void dumpDebug(ProtoOutputStream proto, long fieldId);
 
     /**
-     * Returns whether a given window type is considered a top level one.
-     * A top level window does not have a container, i.e. attached window,
-     * or if it has a container it is laid out as a top-level window, not
-     * as a child of its container.
-     *
-     * @param windowType The window type.
-     * @return True if the window is a top level one.
-     */
-    public boolean isTopLevelWindow(int windowType);
-
-    /**
      * Notifies the keyguard to start fading out.
      *
      * @param startTime the start time of the animation in uptime milliseconds
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 882ed1b..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;
@@ -89,11 +90,13 @@
 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;
@@ -238,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;
@@ -380,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.
@@ -3737,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));
@@ -3952,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);
@@ -3959,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);
@@ -4262,6 +4323,7 @@
         if (wcd != null) {
             wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
         }
+
         proto.flush();
     }
 
@@ -5022,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 {
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index 2b793c8..f204aa2 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -52,6 +52,9 @@
     /** The maximum size of signature file.  This is just to avoid potential abuse. */
     private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
 
+    /** SHA256 hash size. */
+    private static final int HASH_SIZE_BYTES = 32;
+
     private static final boolean DEBUG = false;
 
     /** Returns true if the given file looks like containing an fs-verity signature. */
@@ -90,8 +93,23 @@
         return (retval == 1);
     }
 
+    /** Returns hash of a root node for the fs-verity enabled file. */
+    public static byte[] getFsverityRootHash(@NonNull String filePath) {
+        byte[] result = new byte[HASH_SIZE_BYTES];
+        int retval = measureFsverityNative(filePath, result);
+        if (retval < 0) {
+            if (retval != -OsConstants.ENODATA) {
+                Slog.e(TAG, "Failed to measure fs-verity, errno " + -retval + ": " + filePath);
+            }
+            return null;
+        }
+        return result;
+    }
+
     private static native int enableFsverityNative(@NonNull String filePath,
             @NonNull byte[] pkcs7Signature);
+    private static native int measureFsverityNative(@NonNull String filePath,
+            @NonNull byte[] digest);
     private static native int statxForFsverityNative(@NonNull String filePath);
 
     /**
diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java
index 6f5afa2..d25ddf8 100644
--- a/services/core/java/com/android/server/slice/SliceFullAccessList.java
+++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java
@@ -101,7 +101,7 @@
     public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
         // upgrade xml
         int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
-        final List<UserInfo> activeUsers = UserManager.get(mContext).getUsers(true);
+        final List<UserInfo> activeUsers = UserManager.get(mContext).getAliveUsers();
         for (UserInfo userInfo : activeUsers) {
             upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
         }
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/timezonedetector/CallerIdentityInjector.java b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
new file mode 100644
index 0000000..1500cfa
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
@@ -0,0 +1,62 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.UserHandle;
+
+/**
+ * An interface to wrap various difficult-to-intercept calls that services make to access / manage
+ * caller identity, e.g. {@link Binder#clearCallingIdentity()}.
+ */
+public interface CallerIdentityInjector {
+
+    /** A singleton for the real implementation of {@link CallerIdentityInjector}. */
+    CallerIdentityInjector REAL = new Real();
+
+    /** A {@link UserHandle#getCallingUserId()} call. */
+    @UserIdInt int getCallingUserId();
+
+    /** A {@link Binder#clearCallingIdentity()} call. */
+    long clearCallingIdentity();
+
+    /** A {@link Binder#restoreCallingIdentity(long)} ()} call. */
+    void restoreCallingIdentity(long token);
+
+    /** The real implementation of {@link CallerIdentityInjector}. */
+    class Real implements CallerIdentityInjector {
+
+        protected Real() {
+        }
+
+        @Override
+        public int getCallingUserId() {
+            return UserHandle.getCallingUserId();
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return Binder.clearCallingIdentity();
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
copy to services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
index 114c30e..4c7b1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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.
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.pip.phone.dagger;
+package com.android.server.timezonedetector;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface PipMenuActivityClass {
+/**
+ * A listener used to receive notification that time zone configuration has changed.
+ */
+@FunctionalInterface
+public interface ConfigurationChangeListener {
+    /** Called when the current user or a configuration value has changed. */
+    void onChange();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
new file mode 100644
index 0000000..aee3d8d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.timezonedetector.TimeZoneConfiguration;
+
+import java.util.Objects;
+
+/**
+ * Holds all configuration values that affect time zone behavior and some associated logic, e.g.
+ * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
+ * #createCapabilities()}.
+ */
+public final class ConfigurationInternal {
+
+    private final @UserIdInt int mUserId;
+    private final boolean mUserConfigAllowed;
+    private final boolean mAutoDetectionSupported;
+    private final boolean mAutoDetectionEnabled;
+    private final boolean mLocationEnabled;
+    private final boolean mGeoDetectionEnabled;
+
+    private ConfigurationInternal(Builder builder) {
+        mUserId = builder.mUserId;
+        mUserConfigAllowed = builder.mUserConfigAllowed;
+        mAutoDetectionSupported = builder.mAutoDetectionSupported;
+        mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+        mLocationEnabled = builder.mLocationEnabled;
+        mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+    }
+
+    /** Returns the ID of the user this configuration is associated with. */
+    public @UserIdInt int getUserId() {
+        return mUserId;
+    }
+
+    /** Returns true if the user allowed to modify time zone configuration. */
+    public boolean isUserConfigAllowed() {
+        return mUserConfigAllowed;
+    }
+
+    /** Returns true if the device supports some form of auto time zone detection. */
+    public boolean isAutoDetectionSupported() {
+        return mAutoDetectionSupported;
+    }
+
+    /** Returns the value of the auto time zone detection enabled setting. */
+    public boolean getAutoDetectionEnabledSetting() {
+        return mAutoDetectionEnabled;
+    }
+
+    /**
+     * Returns true if auto time zone detection behavior is actually enabled, which can be distinct
+     * from the raw setting value. */
+    public boolean getAutoDetectionEnabledBehavior() {
+        return mAutoDetectionSupported && mAutoDetectionEnabled;
+    }
+
+    /** Returns true if user's location can be used generally. */
+    public boolean isLocationEnabled() {
+        return mLocationEnabled;
+    }
+
+    /** Returns the value of the geolocation time zone detection enabled setting. */
+    public boolean getGeoDetectionEnabledSetting() {
+        return mGeoDetectionEnabled;
+    }
+
+    /**
+     * Returns true if geolocation time zone detection behavior is actually enabled, which can be
+     * distinct from the raw setting value.
+     */
+    public boolean getGeoDetectionEnabledBehavior() {
+        if (getAutoDetectionEnabledBehavior()) {
+            return mLocationEnabled && mGeoDetectionEnabled;
+        }
+        return false;
+    }
+
+    /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
+    public TimeZoneCapabilities createCapabilities() {
+        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
+                .setConfiguration(asConfiguration());
+
+        boolean allowConfigDateTime = isUserConfigAllowed();
+
+        // Automatic time zone detection is only supported on devices if there is a telephony
+        // network available or geolocation time zone detection is possible.
+        boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
+
+        final int configureAutoDetectionEnabledCapability;
+        if (!deviceHasTimeZoneDetection) {
+            configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+        } else if (!allowConfigDateTime) {
+            configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+        } else {
+            configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+
+        final int configureGeolocationDetectionEnabledCapability;
+        if (!deviceHasTimeZoneDetection) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+        } else if (!allowConfigDateTime) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
+        } else if (!isLocationEnabled()) {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
+        } else {
+            configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+
+        // 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
+        // user is unable to disable auto detection.
+        final int suggestManualTimeZoneCapability;
+        if (!allowConfigDateTime) {
+            suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED;
+        } else if (getAutoDetectionEnabledBehavior()) {
+            suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE;
+        } else {
+            suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
+        }
+        builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+
+        return builder.build();
+    }
+
+    /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
+    public TimeZoneConfiguration asConfiguration() {
+        return new TimeZoneConfiguration.Builder(mUserId)
+                .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
+                .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
+                .build();
+    }
+
+    /**
+     * Merges the configuration values from this with any properties set in {@code
+     * newConfiguration}. The new configuration has precedence. Used to apply user updates to
+     * internal configuration.
+     */
+    public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
+        Builder builder = new Builder(this);
+        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+            builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
+        }
+        if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+            builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
+        }
+        return builder.build();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ConfigurationInternal that = (ConfigurationInternal) o;
+        return mUserId == that.mUserId
+                && mUserConfigAllowed == that.mUserConfigAllowed
+                && mAutoDetectionSupported == that.mAutoDetectionSupported
+                && mAutoDetectionEnabled == that.mAutoDetectionEnabled
+                && mLocationEnabled == that.mLocationEnabled
+                && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
+                mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneDetectorConfiguration{"
+                + "mUserId=" + mUserId
+                + "mUserConfigAllowed=" + mUserConfigAllowed
+                + "mAutoDetectionSupported=" + mAutoDetectionSupported
+                + "mAutoDetectionEnabled=" + mAutoDetectionEnabled
+                + "mLocationEnabled=" + mLocationEnabled
+                + "mGeoDetectionEnabled=" + mGeoDetectionEnabled
+                + '}';
+    }
+
+    /**
+     * A Builder for {@link ConfigurationInternal}.
+     */
+    public static class Builder {
+
+        private final @UserIdInt int mUserId;
+        private boolean mUserConfigAllowed;
+        private boolean mAutoDetectionSupported;
+        private boolean mAutoDetectionEnabled;
+        private boolean mLocationEnabled;
+        private boolean mGeoDetectionEnabled;
+
+        /**
+         * Creates a new Builder with only the userId set.
+         */
+        public Builder(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Creates a new Builder by copying values from an existing instance.
+         */
+        public Builder(ConfigurationInternal toCopy) {
+            this.mUserId = toCopy.mUserId;
+            this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
+            this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
+            this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
+            this.mLocationEnabled = toCopy.mLocationEnabled;
+            this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
+        }
+
+        /**
+         * Sets whether the user is allowed to configure time zone settings on this device.
+         */
+        public Builder setUserConfigAllowed(boolean configAllowed) {
+            mUserConfigAllowed = configAllowed;
+            return this;
+        }
+
+        /**
+         * Sets whether automatic time zone detection is supported on this device.
+         */
+        public Builder setAutoDetectionSupported(boolean supported) {
+            mAutoDetectionSupported = supported;
+            return this;
+        }
+
+        /**
+         * Sets the value of the automatic time zone detection enabled setting for this device.
+         */
+        public Builder setAutoDetectionEnabled(boolean enabled) {
+            mAutoDetectionEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the value of the location mode setting for this user.
+         */
+        public Builder setLocationEnabled(boolean enabled) {
+            mLocationEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the value of the geolocation time zone detection setting for this user.
+         */
+        public Builder setGeoDetectionEnabled(boolean enabled) {
+            mGeoDetectionEnabled = enabled;
+            return this;
+        }
+
+        /** Returns a new {@link ConfigurationInternal}. */
+        @NonNull
+        public ConfigurationInternal build() {
+            return new ConfigurationInternal(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
new file mode 100644
index 0000000..91e172c
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timezonedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 0ca36e0..d640323 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -16,24 +16,30 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.content.Intent.ACTION_USER_SWITCHED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.location.LocationManager;
 import android.net.ConnectivityManager;
+import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
 
 import java.util.Objects;
 
@@ -42,103 +48,87 @@
  */
 public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
 
+    private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    private final Context mContext;
-    private final ContentResolver mCr;
-    private final UserManager mUserManager;
+    @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ContentResolver mCr;
+    @NonNull private final UserManager mUserManager;
+    @NonNull private final boolean mGeoDetectionFeatureEnabled;
+    @NonNull private final LocationManager mLocationManager;
+    // @NonNull after setConfigChangeListener() is called.
+    private ConfigurationChangeListener mConfigChangeListener;
 
-    TimeZoneDetectorCallbackImpl(Context context) {
-        mContext = context;
+    TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
+            boolean geoDetectionFeatureEnabled) {
+        mContext = Objects.requireNonNull(context);
+        mHandler = Objects.requireNonNull(handler);
         mCr = context.getContentResolver();
         mUserManager = context.getSystemService(UserManager.class);
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
+
+        // Wire up the change listener. All invocations are performed on the mHandler thread.
+
+        // Listen for the user changing / the user's location mode changing.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_USER_SWITCHED);
+        filter.addAction(LocationManager.MODE_CHANGED_ACTION);
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                handleConfigChangeOnHandlerThread();
+            }
+        }, filter, null, mHandler);
+
+        // Add async callbacks for global settings being changed.
+        ContentResolver contentResolver = mContext.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+                new ContentObserver(mHandler) {
+                    public void onChange(boolean selfChange) {
+                        handleConfigChangeOnHandlerThread();
+                    }
+                });
+
+        // Add async callbacks for user scoped location settings being changed.
+        contentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
+                true,
+                new ContentObserver(mHandler) {
+                    public void onChange(boolean selfChange) {
+                        handleConfigChangeOnHandlerThread();
+                    }
+                }, UserHandle.USER_ALL);
+    }
+
+    private void handleConfigChangeOnHandlerThread() {
+        if (mConfigChangeListener == null) {
+            Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+        }
+        mConfigChangeListener.onChange();
     }
 
     @Override
-    public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        UserHandle userHandle = UserHandle.of(userId);
-        boolean disallowConfigDateTime =
-                mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
-
-        TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userId);
-
-        // Automatic time zone detection is only supported (currently) on devices if there is a
-        // telephony network available.
-        if (!deviceHasTelephonyNetwork()) {
-            builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
-        } else if (disallowConfigDateTime) {
-            builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
-        } else {
-            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
-        // user is unable to disable auto detection.
-        if (disallowConfigDateTime) {
-            builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
-        } else if (isAutoDetectionEnabled()) {
-            builder.setSuggestManualTimeZone(CAPABILITY_NOT_APPLICABLE);
-        } else {
-            builder.setSuggestManualTimeZone(CAPABILITY_POSSESSED);
-        }
-        return builder.build();
+    public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+        mConfigChangeListener = Objects.requireNonNull(listener);
     }
 
     @Override
-    public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return new TimeZoneConfiguration.Builder()
+    public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+        return new ConfigurationInternal.Builder(userId)
+                .setUserConfigAllowed(isUserConfigAllowed(userId))
+                .setAutoDetectionSupported(isAutoDetectionSupported())
                 .setAutoDetectionEnabled(isAutoDetectionEnabled())
-                .setGeoDetectionEnabled(isGeoDetectionEnabled())
+                .setLocationEnabled(isLocationEnabled(userId))
+                .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
                 .build();
     }
 
     @Override
-    public void setConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
-        Objects.requireNonNull(configuration);
-        if (!configuration.isComplete()) {
-            throw new IllegalArgumentException("configuration=" + configuration + " not complete");
-        }
-
-        // Avoid writing auto detection config for devices that do not support auto time zone
-        // 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 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.
-        }
-    }
-
-    @Override
-    public boolean isAutoDetectionEnabled() {
-        // To ensure that TimeZoneConfiguration is "complete" for simplicity, devices that do not
-        // support auto detection have safe, hard coded configuration values that make it look like
-        // auto detection is turned off. It is therefore important that false is returned from this
-        // method for devices that do not support auto time zone detection. Such devices will not
-        // have a UI to turn the auto detection on/off. Returning true could prevent the user
-        // entering information manually. On devices that do support auto time detection the default
-        // is to turn auto detection on.
-        if (isAutoDetectionSupported()) {
-            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
-        }
-        return false;
-    }
-
-    @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;
+    public @UserIdInt int getCurrentUserId() {
+        return LocalServices.getService(ActivityManagerInternal.class).getCurrentUserId();
     }
 
     @Override
@@ -165,8 +155,55 @@
         alarmManager.setTimeZone(zoneId);
     }
 
+    @Override
+    public void storeConfiguration(TimeZoneConfiguration configuration) {
+        Objects.requireNonNull(configuration);
+
+        // Avoid writing the auto detection enabled setting for devices that do not support auto
+        // time zone detection: if we wrote it down then we'd set the value explicitly, which would
+        // prevent detecting "default" later. That might influence what happens on later releases
+        // that support new types of auto detection on the same hardware.
+        if (isAutoDetectionSupported()) {
+            final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
+            setAutoDetectionEnabled(autoDetectionEnabled);
+
+            final int userId = configuration.getUserId();
+            final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
+            setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
+        }
+    }
+
+    private boolean isUserConfigAllowed(@UserIdInt int userId) {
+        UserHandle userHandle = UserHandle.of(userId);
+        return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
+    }
+
     private boolean isAutoDetectionSupported() {
-        return deviceHasTelephonyNetwork();
+        return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
+    }
+
+    private boolean isAutoDetectionEnabled() {
+        return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
+    }
+
+    private void setAutoDetectionEnabled(boolean enabled) {
+        Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0);
+    }
+
+    private boolean isLocationEnabled(@UserIdInt int userId) {
+        return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId));
+    }
+
+    private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
+        final boolean locationEnabled = isLocationEnabled(userId);
+        return Settings.Secure.getIntForUser(mCr,
+                Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+                locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
+    }
+
+    private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
+        Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
+                enabled ? 1 : 0, userId);
     }
 
     private boolean deviceHasTelephonyNetwork() {
@@ -174,4 +211,4 @@
         return mContext.getSystemService(ConnectivityManager.class)
                 .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index fb7a73d..2d50390 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -27,6 +27,12 @@
  */
 public interface TimeZoneDetectorInternal extends Dumpable.Container {
 
+    /** Adds a listener that will be invoked when time zone detection configuration is changed. */
+    void addConfigurationListener(ConfigurationChangeListener listener);
+
+    /** Returns the {@link ConfigurationInternal} for the current user. */
+    ConfigurationInternal getCurrentUserConfigurationInternal();
+
     /**
      * 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
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 15412a0..f0ce827 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.os.Handler;
 
-import com.android.internal.annotations.VisibleForTesting;
-
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -34,18 +34,26 @@
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+    @NonNull private final List<ConfigurationChangeListener> mConfigurationListeners =
+            new ArrayList<>();
 
-    static TimeZoneDetectorInternalImpl create(@NonNull Context context, @NonNull Handler handler,
-            @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
-        return new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
-    }
-
-    @VisibleForTesting
     public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
+
+        // Wire up a change listener so that any downstream listeners can be notified when
+        // the configuration changes for any reason.
+        mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
+    }
+
+    private void handleConfigurationChanged() {
+        synchronized (mConfigurationListeners) {
+            for (ConfigurationChangeListener listener : mConfigurationListeners) {
+                listener.onChange();
+            }
+        }
     }
 
     @Override
@@ -54,6 +62,19 @@
     }
 
     @Override
+    public void addConfigurationListener(ConfigurationChangeListener listener) {
+        synchronized (mConfigurationListeners) {
+            mConfigurationListeners.add(Objects.requireNonNull(listener));
+        }
+    }
+
+    @Override
+    @NonNull
+    public ConfigurationInternal getCurrentUserConfigurationInternal() {
+        return mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal();
+    }
+
+    @Override
     public void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         Objects.requireNonNull(timeZoneSuggestion);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index d81f949..7501d9f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -24,32 +24,24 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.UserHandle;
-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;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.StrategyListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.Objects;
 
 /**
@@ -65,6 +57,9 @@
 
     private static final String TAG = "TimeZoneDetectorService";
 
+    /** A compile time switch for enabling / disabling geolocation-based time zone detection. */
+    private static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
+
     /**
      * Handles the service lifecycle for {@link TimeZoneDetectorService} and
      * {@link TimeZoneDetectorInternalImpl}.
@@ -80,18 +75,20 @@
             // Obtain / create the shared dependencies.
             Context context = getContext();
             Handler handler = FgThread.getHandler();
+
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(context);
+                    TimeZoneDetectorStrategyImpl.create(
+                            context, handler, GEOLOCATION_TIME_ZONE_DETECTION_ENABLED);
 
             // Create and publish the local service for use by internal callers.
             TimeZoneDetectorInternal internal =
-                    TimeZoneDetectorInternalImpl.create(context, handler, timeZoneDetectorStrategy);
+                    new TimeZoneDetectorInternalImpl(context, handler, timeZoneDetectorStrategy);
             publishLocalService(TimeZoneDetectorInternal.class, internal);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
-            TimeZoneDetectorService service =
-                    TimeZoneDetectorService.create(context, handler, timeZoneDetectorStrategy);
+            TimeZoneDetectorService service = TimeZoneDetectorService.create(
+                    context, handler, timeZoneDetectorStrategy);
             publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
         }
     }
@@ -103,52 +100,38 @@
     private final Handler mHandler;
 
     @NonNull
+    private final CallerIdentityInjector mCallerIdentityInjector;
+
+    @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 SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners =
-            new SparseArray<>();
+    private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
+            new ArrayList<>();
 
     private static TimeZoneDetectorService create(
             @NonNull Context context, @NonNull Handler handler,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
 
-        TimeZoneDetectorService service =
-                new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
-
-        ContentResolver contentResolver = context.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
-                new ContentObserver(handler) {
-                    public void onChange(boolean selfChange) {
-                        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.
+        CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
+        TimeZoneDetectorService service = new TimeZoneDetectorService(
+                context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
         return service;
     }
 
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
+            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
+        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
-        mTimeZoneDetectorStrategy.setStrategyListener(new StrategyListener() {
-            @Override
-            public void onConfigurationChanged() {
-                handleConfigurationChanged();
-            }
-        });
+
+        // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+        // the configuration changes for any reason.
+        mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
     }
 
     @Override
@@ -156,26 +139,12 @@
     public TimeZoneCapabilities getCapabilities() {
         enforceManageTimeZoneDetectorConfigurationPermission();
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.getCapabilities(userId);
+            return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
         } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration() {
-        enforceManageTimeZoneDetectorConfigurationPermission();
-
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
-        try {
-            return mTimeZoneDetectorStrategy.getConfiguration(userId);
-        } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -184,12 +153,16 @@
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(configuration);
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int callingUserId = mCallerIdentityInjector.getCallingUserId();
+        if (callingUserId != configuration.getUserId()) {
+            return false;
+        }
+
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            return mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+            return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -197,25 +170,17 @@
     public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(listener);
-        int userId = UserHandle.getCallingUserId();
 
         synchronized (mConfigurationListeners) {
-            ArrayList<ITimeZoneConfigurationListener> listeners =
-                    mConfigurationListeners.get(userId);
-            if (listeners != null && listeners.contains(listener)) {
+            if (mConfigurationListeners.contains(listener)) {
                 return;
             }
             try {
-                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().
-                listeners.add(listener);
+                mConfigurationListeners.add(listener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
             }
@@ -226,19 +191,16 @@
     public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
         enforceManageTimeZoneDetectorConfigurationPermission();
         Objects.requireNonNull(listener);
-        int userId = UserHandle.getCallingUserId();
 
         synchronized (mConfigurationListeners) {
             boolean removedListener = false;
-            ArrayList<ITimeZoneConfigurationListener> userListeners =
-                    mConfigurationListeners.get(userId);
-            if (userListeners.remove(listener)) {
+            if (mConfigurationListeners.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
+                Slog.w(TAG, "Client asked to remove listener=" + listener
                         + ", but no listeners were removed."
                         + " mConfigurationListeners=" + mConfigurationListeners);
             }
@@ -259,19 +221,14 @@
     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;
-                    }
+            final int listenerCount = mConfigurationListeners.size();
+            for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
+                ITimeZoneConfigurationListener listener =
+                        mConfigurationListeners.get(listenerIndex);
+                if (listener.asBinder().equals(who)) {
+                    mConfigurationListeners.remove(listenerIndex);
+                    removedListener = true;
+                    break;
                 }
             }
             if (!removedListener) {
@@ -283,42 +240,25 @@
     }
 
     void handleConfigurationChanged() {
-        // Note: we could trigger an async time zone detection operation here via a call to
-        // 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.
-
         // 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) {
-            final int userCount = mConfigurationListeners.size();
-            for (int userIndex = 0; userIndex < userCount; userIndex++) {
-                int userId = mConfigurationListeners.keyAt(userIndex);
-                TimeZoneConfiguration configuration =
-                        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);
-                    }
+            final int listenerCount = mConfigurationListeners.size();
+            for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
+                ITimeZoneConfigurationListener listener =
+                        mConfigurationListeners.get(listenerIndex);
+                try {
+                    listener.onChange();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to notify listener=" + listener, e);
                 }
             }
         }
     }
 
     /** Provided for command-line access. This is not exposed as a binder API. */
-    void suggestGeolocationTimeZone(
-            @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+    void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         enforceSuggestGeolocationTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
 
@@ -331,12 +271,12 @@
         enforceSuggestManualTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
 
-        int userId = UserHandle.getCallingUserId();
-        long token = Binder.clearCallingIdentity();
+        int userId = mCallerIdentityInjector.getCallingUserId();
+        long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -358,12 +298,6 @@
         ipw.flush();
     }
 
-    /** Internal method for handling the auto time zone configuration being changed. */
-    @VisibleForTesting
-    public void handleAutoTimeZoneConfigChanged() {
-        mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneConfigChanged);
-    }
-
     private void enforceManageTimeZoneDetectorConfigurationPermission() {
         // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
         mContext.enforceCallingPermission(
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index c5b7e39..f944c56 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -19,51 +19,84 @@
 import android.annotation.UserIdInt;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
 /**
- * The interface for the class that implements the time detection algorithm used by the
- * {@link TimeZoneDetectorService}.
+ * The interface for the class that is responsible for setting the time zone on a device, used by
+ * {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
  *
- * <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
- * and what to set it to.
+ * <p>The strategy receives suggestions, which it may use to modify the device's time zone setting.
+ * Suggestions are acted on or ignored as needed, depending on previously received suggestions and
+ * the current user's configuration (see {@link ConfigurationInternal}).
  *
- * <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.
+ * <p>Devices can have zero, one or two automatic time zone detection algorithm available at any
+ * point in time.
+ *
+ * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * availability and use depends on several factors:
+ * <ul>
+ * <li>Telephony is only available on devices with a telephony stack.
+ * <li>Geolocation is also optional and configured at image creation time. When enabled on a
+ * device, its availability depends on the current user's settings, so switching between users can
+ * change the automatic algorithm used by the device.</li>
+ * </ul>
+ *
+ * <p>If there are no automatic time zone detections algorithms available then the user can usually
+ * change the device time zone manually. Under most circumstances the current user can turn
+ * automatic time zone detection on or off, or choose the algorithm via settings.
+ *
+ * <p>Telephony detection is independent of the current user. The device keeps track of the most
+ * recent telephony suggestion from each slotIndex. When telephony detection is in use, the highest
+ * scoring suggestion is used to set the device time zone based on a scoring algorithm. If several
+ * slotIndexes provide the same score then the slotIndex with the lowest numeric value "wins". If
+ * the situation changes and it is no longer possible to be confident about the time zone,
+ * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
+ * suggestion otherwise it will remain in use.
+ *
+ * <p>Geolocation detection is dependent on the current user and their settings. The device retains
+ * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
+ * user's "location toggle", but even when that is enabled the user may choose to enable / disable
+ * the use of geolocation for device time zone detection. If the current user changes to one that
+ * does not have geolocation detection enabled, or the user turns off geolocation detection, then
+ * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
+ * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
+ * will remain in use.
+ *
+ * <p>Threading:
+ *
+ * <p>Suggestion calls with a void return type may be handed off to a separate thread and handled
+ * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, and debug
+ * calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread
+ * concurrently with other operations.
  *
  * @hide
  */
 public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
 
-    /** A listener for strategy events. */
-    interface StrategyListener {
-        /**
-         * Invoked when configuration has been changed.
-         */
-        void onConfigurationChanged();
-    }
+    /**
+     * Sets a listener that will be triggered whenever time zone detection configuration is
+     * changed.
+     */
+    void addConfigChangeListener(@NonNull ConfigurationChangeListener listener);
 
-    /** Sets the listener that enables the strategy to communicate with the surrounding service. */
-    void setStrategyListener(@NonNull StrategyListener listener);
-
-    /** Returns the user's time zone capabilities. */
+    /** Returns the user's time zone configuration. */
     @NonNull
-    TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
+    ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
 
     /**
-     * Returns the configuration that controls time zone detector behavior.
+     * Returns the configuration that controls time zone detector behavior for the current user.
      */
     @NonNull
-    TimeZoneConfiguration getConfiguration(@UserIdInt int userId);
+    ConfigurationInternal getCurrentUserConfigurationInternal();
 
     /**
-     * Updates the configuration settings that control time zone detector behavior.
+     * Updates the configuration properties that control a device's time zone behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed,
+     * {@code false} otherwise.
      */
-    boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
+    boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
 
     /**
      * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
@@ -85,9 +118,4 @@
      * suggestion.
      */
     void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
-
-    /**
-     * Called when there has been a change to the automatic time zone detection configuration.
-     */
-    void handleAutoTimeZoneConfigChanged();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index d1369a2..8a42b18 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -20,10 +20,7 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-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;
@@ -33,6 +30,7 @@
 import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
+import android.os.Handler;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.Slog;
@@ -45,15 +43,7 @@
 import java.util.Objects;
 
 /**
- * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
- * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
- * zone detection" setting.
- *
- * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each
- * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes
- * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation
- * changes and it is no longer possible to be confident about the time zone, slotIndexes must have
- * an empty suggestion submitted in order to "withdraw" their previous suggestion.
+ * The real implementation of {@link TimeZoneDetectorStrategy}.
  *
  * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
  */
@@ -61,48 +51,27 @@
 
     /**
      * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
-     * / system properties. It can be faked for testing different scenarios.
+     * / system properties. It can be faked for testing.
      *
      * <p>Note: Because the settings / system properties-derived values can currently be modified
-     * independently and from different threads (and processes!), their use are prone to race
-     * conditions. That will be true until the responsibility for setting their values is moved to
-     * {@link TimeZoneDetectorStrategyImpl} (which is thread safe).
+     * independently and from different threads (and processes!), their use is prone to race
+     * conditions.
      */
     @VisibleForTesting
     public interface Callback {
 
         /**
-         * Returns the capabilities for the user.
+         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
+         * changes that could affect time zone detection. This is invoked during system server
+         * setup.
          */
-        @NonNull
-        TimeZoneCapabilities getCapabilities(@UserIdInt int userId);
+        void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
 
-        /**
-         * Returns the configuration for the user.
-         * @param userId
-         */
-        @NonNull
-        TimeZoneConfiguration getConfiguration(int userId);
+        /** Returns the current user at the instant it is called. */
+        @UserIdInt int getCurrentUserId();
 
-        /**
-         * Sets the configuration for the user. This method handles storage only, the configuration
-         * must have been validated by the caller and be complete.
-         *
-         * @throws IllegalArgumentException if {@link TimeZoneConfiguration#isComplete()}
-         *     returns {@code false}
-         */
-        void setConfiguration(@UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
-
-        /**
-         * Returns true if automatic time zone detection is currently enabled.
-         */
-        boolean isAutoDetectionEnabled();
-
-        /**
-         * Returns whether geolocation can be used for time zone detection when {@link
-         * #isAutoDetectionEnabled()} returns {@code true}.
-         */
-        boolean isGeoDetectionEnabled();
+        /** Returns the {@link ConfigurationInternal} for the specified user. */
+        ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
 
         /**
          * Returns true if the device has had an explicit time zone set.
@@ -118,6 +87,13 @@
          * Sets the device's time zone.
          */
         void setDeviceTimeZone(@NonNull String zoneId);
+
+        /**
+         * Stores the configuration properties contained in {@code newConfiguration}.
+         * All checks about user capabilities must be done by the caller and
+         * {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
+         */
+        void storeConfiguration(TimeZoneConfiguration newConfiguration);
     }
 
     private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -189,9 +165,9 @@
     @NonNull
     private final Callback mCallback;
 
-    /** Non-null after {@link #setStrategyListener(StrategyListener)} is called. */
-    @Nullable
-    private StrategyListener mListener;
+    @GuardedBy("this")
+    @NonNull
+    private List<ConfigurationChangeListener> mConfigChangeListeners = new ArrayList<>();
 
     /**
      * A log that records the decisions / decision metadata that affected the device's time zone.
@@ -211,7 +187,8 @@
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     /**
-     * The latest geolocation suggestion received.
+     * The latest geolocation suggestion received. If the user disabled geolocation time zone
+     * detection then the latest suggestion is cleared.
      */
     @GuardedBy("this")
     private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
@@ -223,113 +200,120 @@
     /**
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
-    public static TimeZoneDetectorStrategyImpl create(Context context) {
-        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
-        return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+    public static TimeZoneDetectorStrategyImpl create(
+            @NonNull Context context, @NonNull Handler handler,
+            boolean geolocationTimeZoneDetectionEnabled) {
+
+        TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
+                context, handler, geolocationTimeZoneDetectionEnabled);
+        return new TimeZoneDetectorStrategyImpl(callback);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(Callback callback) {
+    public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
         mCallback = Objects.requireNonNull(callback);
+        mCallback.setConfigChangeListener(this::handleConfigChanged);
     }
 
     /**
-     * Sets a listener that allows the strategy to communicate with the surrounding service. This
-     * must be called before the instance is used and must only be called once.
+     * Adds a listener that allows the strategy to communicate with the surrounding service /
+     * internal. This must be called before the instance is used.
      */
     @Override
-    public synchronized void setStrategyListener(@NonNull StrategyListener listener) {
-        if (mListener != null) {
-            throw new IllegalStateException("Strategy already has a listener");
-        }
-        mListener = Objects.requireNonNull(listener);
+    public synchronized void addConfigChangeListener(
+            @NonNull ConfigurationChangeListener listener) {
+        Objects.requireNonNull(listener);
+        mConfigChangeListeners.add(listener);
     }
 
     @Override
     @NonNull
-    public synchronized TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        return mCallback.getCapabilities(userId);
+    public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+        return mCallback.getConfigurationInternal(userId);
     }
 
     @Override
     @NonNull
-    public synchronized TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return mCallback.getConfiguration(userId);
+    public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
+        int currentUserId = mCallback.getCurrentUserId();
+        return getConfigurationInternal(currentUserId);
     }
 
     @Override
     public synchronized boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configurationChanges) {
-        Objects.requireNonNull(configurationChanges);
+            @NonNull TimeZoneConfiguration requestedConfiguration) {
+        Objects.requireNonNull(requestedConfiguration);
 
-        // Validate the requested configuration changes before applying any of them.
-        TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
-        boolean canManageTimeZoneDetection =
-                capabilities.getConfigureAutoDetectionEnabled() >= CAPABILITY_NOT_APPLICABLE;
-        if (!canManageTimeZoneDetection
-                && containsAutoTimeDetectionProperties(configurationChanges)) {
+        int userId = requestedConfiguration.getUserId();
+        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+
+        // Create a new configuration builder, and copy across the mutable properties users are
+        // able to modify. Other properties are therefore ignored.
+        final TimeZoneConfiguration newConfiguration =
+                capabilities.applyUpdate(requestedConfiguration);
+        if (newConfiguration == null) {
+            // The changes could not be made due to
             return false;
         }
 
-        // Create a complete configuration by merging the existing and new (possibly partial)
-        // configuration.
-        final TimeZoneConfiguration oldConfiguration = mCallback.getConfiguration(userId);
-        final TimeZoneConfiguration newConfiguration =
-                new TimeZoneConfiguration.Builder(oldConfiguration)
-                        .mergeProperties(configurationChanges)
-                        .build();
+        // Store the configuration / notify as needed. This will cause the mCallback to invoke
+        // handleConfigChanged() asynchronously.
+        mCallback.storeConfiguration(newConfiguration);
 
-        // Set the configuration / notify as needed.
-        boolean configurationChanged = !oldConfiguration.equals(newConfiguration);
-        if (configurationChanged) {
-            mCallback.setConfiguration(userId, newConfiguration);
-
-            String logMsg = "Configuration changed:"
-                    + "oldConfiguration=" + oldConfiguration
-                    + ", configuration=" + configurationChanges
-                    + ", newConfiguration=" + newConfiguration;
-            mTimeZoneChangesLog.log(logMsg);
-            if (DBG) {
-                Slog.d(LOG_TAG, logMsg);
-            }
-            mListener.onConfigurationChanged();
+        TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
+        String logMsg = "Configuration changed:"
+                + " oldConfiguration=" + oldConfiguration
+                + ", newConfiguration=" + newConfiguration;
+        mTimeZoneChangesLog.log(logMsg);
+        if (DBG) {
+            Slog.d(LOG_TAG, logMsg);
         }
         return true;
     }
 
-    private static boolean containsAutoTimeDetectionProperties(
-            @NonNull TimeZoneConfiguration configuration) {
-        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
-                || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
-    }
-
     @Override
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
         if (DBG) {
-            Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+            Slog.d(LOG_TAG, "Geolocation suggestion received."
+                    + " currentUserConfig=" + currentUserConfig
+                    + " newSuggestion=" + suggestion);
         }
-
         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()) {
+        if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
+            // Only store a geolocation suggestion if geolocation detection is currently enabled.
+            mLatestGeoLocationSuggestion.set(suggestion);
+
+            // Now perform auto time zone detection. The new suggestion may be used to modify the
+            // time zone setting.
             String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
-            doAutoTimeZoneDetection(reason);
+            doAutoTimeZoneDetection(currentUserConfig, reason);
         }
     }
 
     @Override
     public synchronized boolean suggestManualTimeZone(
             @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        if (userId != currentUserId) {
+            Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId
+                    + " suggestion=" + suggestion);
+
+            // Only listen to changes from the current user.
+            return false;
+        }
+
         Objects.requireNonNull(suggestion);
 
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilities capabilities = mCallback.getCapabilities(userId);
+        TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
         if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ", capabilities=" + capabilities
@@ -345,8 +329,12 @@
     @Override
     public synchronized void suggestTelephonyTimeZone(
             @NonNull TelephonyTimeZoneSuggestion suggestion) {
+
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
         if (DBG) {
-            Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion);
+            Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
+                    + " newSuggestion=" + suggestion);
         }
         Objects.requireNonNull(suggestion);
 
@@ -360,9 +348,9 @@
 
         // Now perform auto time zone detection. The new suggestion may be used to modify the time
         // zone setting.
-        if (!mCallback.isGeoDetectionEnabled()) {
+        if (!currentUserConfig.getGeoDetectionEnabledBehavior()) {
             String reason = "New telephony time zone suggested. suggestion=" + suggestion;
-            doAutoTimeZoneDetection(reason);
+            doAutoTimeZoneDetection(currentUserConfig, reason);
         }
     }
 
@@ -392,15 +380,15 @@
      * Performs automatic time zone detection.
      */
     @GuardedBy("this")
-    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoDetectionEnabled()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
+    private void doAutoTimeZoneDetection(
+            @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
+        if (!currentUserConfig.getAutoDetectionEnabledBehavior()) {
+            // Avoid doing unnecessary work.
             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()) {
+        // Use the right suggestions based on the current configuration.
+        if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
             doGeolocationTimeZoneDetection(detectionReason);
         } else  {
             doTelephonyTimeZoneDetection(detectionReason);
@@ -480,35 +468,18 @@
 
         // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
         // zone ID.
-        String newZoneId = bestTelephonySuggestion.suggestion.getZoneId();
-        if (newZoneId == null) {
+        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
+        if (zoneId == null) {
             Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
                     + " bestTelephonySuggestion=" + bestTelephonySuggestion
                     + " detectionReason=" + detectionReason);
             return;
         }
 
-        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
         String cause = "Found good suggestion."
                 + ", bestTelephonySuggestion=" + bestTelephonySuggestion
                 + ", detectionReason=" + detectionReason;
-        setAutoDeviceTimeZoneIfRequired(zoneId, cause);
-    }
-
-    @GuardedBy("this")
-    private void setAutoDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
-        Objects.requireNonNull(newZoneId);
-        Objects.requireNonNull(cause);
-
-        if (!mCallback.isAutoDetectionEnabled()) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
-            }
-            return;
-        }
-        setDeviceTimeZoneIfRequired(newZoneId, cause);
+        setDeviceTimeZoneIfRequired(zoneId, cause);
     }
 
     @GuardedBy("this")
@@ -582,13 +553,39 @@
         return findBestTelephonySuggestion();
     }
 
-    @Override
-    public synchronized void handleAutoTimeZoneConfigChanged() {
+    private synchronized void handleConfigChanged() {
         if (DBG) {
-            Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
+            Slog.d(LOG_TAG, "handleConfigChanged()");
         }
 
-        doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
+        clearGeolocationSuggestionIfNeeded();
+
+        for (ConfigurationChangeListener listener : mConfigChangeListeners) {
+            listener.onChange();
+        }
+    }
+
+    @GuardedBy("this")
+    private void clearGeolocationSuggestionIfNeeded() {
+        // This method is called whenever the user changes or the config for any user changes. We
+        // don't know what happened, so we capture the current user's config, check to see if we
+        // need to clear state associated with a previous user, and rerun detection.
+        int currentUserId = mCallback.getCurrentUserId();
+        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+
+        GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
+                mLatestGeoLocationSuggestion.get();
+        if (latestGeoLocationSuggestion != null
+                && !currentUserConfig.getGeoDetectionEnabledBehavior()) {
+            // The current user's config has geodetection disabled, so clear the latest suggestion.
+            // This is done to ensure we only ever keep a geolocation suggestion if the user has
+            // said it is ok to do so.
+            mLatestGeoLocationSuggestion.set(null);
+            mTimeZoneChangesLog.log(
+                    "clearGeolocationSuggestionIfNeeded: Cleared latest Geolocation suggestion.");
+        }
+
+        doAutoTimeZoneDetection(currentUserConfig, "clearGeolocationSuggestionIfNeeded()");
     }
 
     @Override
@@ -604,11 +601,14 @@
         ipw.println("TimeZoneDetectorStrategy:");
 
         ipw.increaseIndent(); // level 1
-        ipw.println("mCallback.isAutoDetectionEnabled()=" + mCallback.isAutoDetectionEnabled());
+        int currentUserId = mCallback.getCurrentUserId();
+        ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
+        ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
+        ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
+        ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
         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
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 6adff0d..386f390 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -379,7 +379,7 @@
     }
 
     private void updateTrustAll() {
-        List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+        List<UserInfo> userInfos = mUserManager.getAliveUsers();
         for (UserInfo userInfo : userInfos) {
             updateTrust(userInfo.id, 0);
         }
@@ -485,7 +485,7 @@
 
         List<UserInfo> userInfos;
         if (userIdOrAll == UserHandle.USER_ALL) {
-            userInfos = mUserManager.getUsers(true /* excludeDying */);
+            userInfos = mUserManager.getAliveUsers();
         } else {
             userInfos = new ArrayList<>();
             userInfos.add(mUserManager.getUserInfo(userIdOrAll));
@@ -644,7 +644,7 @@
         }
         List<UserInfo> userInfos;
         if (userId == UserHandle.USER_ALL) {
-            userInfos = mUserManager.getUsers(true /* excludeDying */);
+            userInfos = mUserManager.getAliveUsers();
         } else {
             userInfos = new ArrayList<>();
             userInfos.add(mUserManager.getUserInfo(userId));
@@ -1156,7 +1156,7 @@
         }
 
         private void enforceListenerPermission() {
-            mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.TRUST_LISTENER,
                     "register trust listener");
         }
 
@@ -1171,7 +1171,7 @@
                 fout.println("disabled because the third-party apps can't run yet.");
                 return;
             }
-            final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             mHandler.runWithScissors(new Runnable() {
                 @Override
                 public void run() {
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index a106dc6..0b0bb70 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -258,7 +258,7 @@
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
         final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
-        final int packageUid = pm.getPackageUidInternal(packageName,
+        final int packageUid = pm.getPackageUid(packageName,
                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUserId);
         if (packageUid != callingUid) {
             throw new SecurityException(
@@ -336,7 +336,7 @@
         if (toPackage != null) {
             mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
                     "takePersistableUriPermission");
-            uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
+            uid = mPmInternal.getPackageUid(toPackage, 0 /* flags */, userId);
         } else {
             enforceNotIsolatedCaller("takePersistableUriPermission");
             uid = Binder.getCallingUid();
@@ -401,7 +401,7 @@
         if (toPackage != null) {
             mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
                     "releasePersistableUriPermission");
-            uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
+            uid = mPmInternal.getPackageUid(toPackage, 0 /* flags */ , userId);
         } else {
             enforceNotIsolatedCaller("releasePersistableUriPermission");
             uid = Binder.getCallingUid();
@@ -600,7 +600,7 @@
         if (needed != null) {
             targetUid = needed.targetUid;
         } else {
-            targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+            targetUid = mPmInternal.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
                     targetUserId);
             if (targetUid < 0) {
                 if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg
@@ -690,7 +690,7 @@
                         final ProviderInfo pi = getProviderInfo(uri.getAuthority(), sourceUserId,
                                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
                         if (pi != null && sourcePkg.equals(pi.packageName)) {
-                            int targetUid = mPmInternal.getPackageUidInternal(
+                            int targetUid = mPmInternal.getPackageUid(
                                         targetPkg, MATCH_UNINSTALLED_PACKAGES, targetUserId);
                             if (targetUid != -1) {
                                 final GrantUri grantUri = new GrantUri(sourceUserId, uri,
@@ -787,7 +787,7 @@
         if (targetPkg == null) {
             throw new NullPointerException("targetPkg");
         }
-        int targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+        int targetUid = mPmInternal.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
                 targetUserId);
 
         targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, modeFlags,
@@ -1108,7 +1108,7 @@
 
         int targetUid = lastTargetUid;
         if (targetUid < 0 && targetPkg != null) {
-            targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+            targetUid = mPmInternal.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
                     UserHandle.getUserId(callingUid));
             if (targetUid < 0) {
                 if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg);
@@ -1462,7 +1462,8 @@
                     boolean printed = false;
                     int dumpUid = -2;
                     if (dumpPackage != null) {
-                        dumpUid = mPmInternal.getPackageUidInternal(dumpPackage, MATCH_ANY_USER, 0);
+                        dumpUid = mPmInternal.getPackageUid(dumpPackage,
+                                MATCH_ANY_USER, 0 /* userId */);
                     }
                     for (int i = 0; i < mGrantedUriPermissions.size(); i++) {
                         int uid = mGrantedUriPermissions.keyAt(i);
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 1536473..4c2d0d0 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -700,8 +700,8 @@
                     touchableRegion.getBounds(touchableFrame);
                     RectF windowFrame = mTempRectF;
                     windowFrame.set(touchableFrame);
-                    windowFrame.offset(-windowState.getFrameLw().left,
-                            -windowState.getFrameLw().top);
+                    windowFrame.offset(-windowState.getFrame().left,
+                            -windowState.getFrame().top);
                     matrix.mapRect(windowFrame);
                     Region windowBounds = mTempRegion2;
                     windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
@@ -730,7 +730,7 @@
                     }
 
                     // Count letterbox into nonMagnifiedBounds
-                    if (windowState.isLetterboxedForDisplayCutoutLw()) {
+                    if (windowState.isLetterboxedForDisplayCutout()) {
                         Region letterboxBounds = getLetterboxBounds(windowState);
                         nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
                         availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
@@ -1429,11 +1429,11 @@
                         // Account for all space in the task, whether the windows in it are
                         // touchable or not. The modal window blocks all touches from the task's
                         // area.
-                        unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
+                        unaccountedSpace.op(windowState.getDisplayFrame(), unaccountedSpace,
                                 Region.Op.REVERSE_DIFFERENCE);
                     } else {
                         // If a window has tap exclude region, we need to account it.
-                        final Region displayRegion = new Region(windowState.getDisplayFrameLw());
+                        final Region displayRegion = new Region(windowState.getDisplayFrame());
                         final Region tapExcludeRegion = new Region();
                         windowState.getTapExcludeRegion(tapExcludeRegion);
                         displayRegion.op(tapExcludeRegion, displayRegion,
@@ -1470,7 +1470,7 @@
                 // Move to origin as all transforms are captured by the matrix.
                 RectF windowFrame = mTempRectF;
                 windowFrame.set(rect);
-                windowFrame.offset(-windowState.getFrameLw().left, -windowState.getFrameLw().top);
+                windowFrame.offset(-windowState.getFrame().left, -windowState.getFrame().top);
 
                 matrix.mapRect(windowFrame);
 
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 e4f2854..56261c4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -110,9 +110,13 @@
 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_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTAINERS;
+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_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
 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;
@@ -145,9 +149,6 @@
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 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_CONFIGURATION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
@@ -499,7 +500,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 +564,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();
 
@@ -927,7 +927,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);
         }
@@ -1097,15 +1097,15 @@
 
     private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
         if (!attachedToProcess()) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.w(TAG,
-                    "Can't report activity moved to display - client not running, activityRecord="
-                            + this + ", displayId=" + displayId);
+            ProtoLog.w(WM_DEBUG_SWITCH, "Can't report activity moved "
+                    + "to display - client not running, activityRecord=%s, displayId=%d",
+                    this, displayId);
             return;
         }
         try {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
-                    "Reporting activity moved to display" + ", activityRecord=" + this
-                            + ", displayId=" + displayId + ", config=" + config);
+            ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
+                    + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
+                    config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     MoveToDisplayItem.obtain(displayId, config));
@@ -1116,14 +1116,13 @@
 
     private void scheduleConfigurationChanged(Configuration config) {
         if (!attachedToProcess()) {
-            if (DEBUG_CONFIGURATION) Slog.w(TAG,
-                    "Can't report activity configuration update - client not running"
-                            + ", activityRecord=" + this);
+            ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
+                    + "update - client not running, activityRecord=%s", this);
             return;
         }
         try {
-            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
-                    + config);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+                    + "config: %s", this, config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     ActivityConfigurationChangeItem.obtain(config));
@@ -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;
     }
@@ -1343,7 +1334,7 @@
         if (w == null || winHint != null && w != winHint) {
             return;
         }
-        final boolean surfaceReady = w.isDrawnLw()  // Regular case
+        final boolean surfaceReady = w.isDrawn()  // Regular case
                 || w.mWinAnimator.mSurfaceDestroyDeferred  // The preserved surface is still ready.
                 || w.isDragResizeChanged();  // Waiting for relayoutWindow to call preserveSurface.
         final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent();
@@ -1364,7 +1355,7 @@
                     : inMultiWindowMode()
                             ? task.getBounds()
                             : getRootTask().getParent().getBounds();
-            mLetterbox.layout(spaceToFill, w.getFrameLw(), mTmpPoint);
+            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
         } else if (mLetterbox != null) {
             mLetterbox.hide();
         }
@@ -1591,7 +1582,6 @@
         keysPaused = false;
         inHistory = false;
         nowVisible = false;
-        mDrawn = false;
         mClientVisible = true;
         idle = false;
         hasBeenLaunched = false;
@@ -1959,10 +1949,8 @@
             startingWindow = null;
             startingDisplayed = false;
             if (surface == null) {
-                ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
-                        "startingWindow was set but startingSurface==null, couldn't "
-                                + "remove");
-
+                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+                        + "startingSurface==null, couldn't remove");
                 return;
             }
         } else {
@@ -1972,9 +1960,10 @@
             return;
         }
 
+
         ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
-                        + " startingView=%s Callers=%s",
-                this, startingWindow, startingSurface, Debug.getCallers(5));
+                + " startingView=%s Callers=%s", this, startingWindow, startingSurface,
+                Debug.getCallers(5));
 
 
         // Use the same thread to remove the window as we used to add it, as otherwise we end up
@@ -2409,9 +2398,8 @@
      */
     boolean moveFocusableActivityToTop(String reason) {
         if (!isFocusable()) {
-            if (DEBUG_FOCUS) {
-                Slog.d(TAG_FOCUS, "moveActivityStackToFront: unfocusable activity=" + this);
-            }
+            ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: unfocusable "
+                    + "activity=%s", this);
             return false;
         }
 
@@ -2424,15 +2412,11 @@
 
         if (mRootWindowContainer.getTopResumedActivity() == this
                 && getDisplayContent().mFocusedApp == this) {
-            if (DEBUG_FOCUS) {
-                Slog.d(TAG_FOCUS, "moveActivityStackToFront: already on top, activity=" + this);
-            }
+            ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: already on top, "
+                    + "activity=%s", this);
             return !isState(RESUMED);
         }
-
-        if (DEBUG_FOCUS) {
-            Slog.d(TAG_FOCUS, "moveActivityStackToFront: activity=" + this);
-        }
+        ProtoLog.d(WM_DEBUG_FOCUS, "moveActivityStackToFront: activity=%s", this);
 
         stack.moveToFront(reason, task);
         // Report top activity change to tracking services and WM
@@ -2796,8 +2780,7 @@
         }
         makeFinishingLocked();
 
-        final boolean activityRemoved = destroyImmediately(true /* removeFromApp */,
-                "finish-imm:" + reason);
+        final boolean activityRemoved = destroyImmediately("finish-imm:" + reason);
 
         // If the display does not have running activity, the configuration may need to be
         // updated for restoring original orientation of the display.
@@ -2809,10 +2792,8 @@
             mRootWindowContainer.resumeFocusedStacksTopActivities();
         }
 
-        if (DEBUG_CONTAINERS) {
-            Slog.d(TAG_CONTAINERS, "destroyIfPossible: r=" + this + " destroy returned removed="
-                    + activityRemoved);
-        }
+        ProtoLog.d(WM_DEBUG_CONTAINERS, "destroyIfPossible: r=%s destroy returned "
+                + "removed=%s", this, activityRemoved);
 
         return activityRemoved;
     }
@@ -2845,7 +2826,7 @@
      * @return {@code true} if activity was immediately removed from history, {@code false}
      * otherwise.
      */
-    boolean destroyImmediately(boolean removeFromApp, String reason) {
+    boolean destroyImmediately(String reason) {
         if (DEBUG_SWITCH || DEBUG_CLEANUP) {
             Slog.v(TAG_SWITCH, "Removing activity from " + reason + ": token=" + this
                     + ", app=" + (hasProcess() ? app.mName : "(null)"));
@@ -2867,17 +2848,9 @@
         cleanUp(false /* cleanServices */, false /* setState */);
 
         if (hasProcess()) {
-            if (removeFromApp) {
-                app.removeActivity(this, true /* keepAssociation */);
-                if (!app.hasActivities()) {
-                    mAtmService.clearHeavyWeightProcessIfEquals(app);
-                    // Update any services we are bound to that might care about whether
-                    // their client may have activities.
-                    // No longer have activities, so update LRU list and oom adj.
-                    app.updateProcessInfo(true /* updateServiceConnectionActivities */,
-                            false /* activityChange */, true /* updateOomAdj */,
-                            false /* addPendingTopUid */);
-                }
+            app.removeActivity(this, true /* keepAssociation */);
+            if (!app.hasActivities()) {
+                mAtmService.clearHeavyWeightProcessIfEquals(app);
             }
 
             boolean skipDestroy = false;
@@ -2944,7 +2917,7 @@
                         + " pausing=" + stack.mPausingActivity
                         + " for reason " + reason);
             }
-            return destroyImmediately(true /* removeFromApp */, reason);
+            return destroyImmediately(reason);
         }
         return false;
     }
@@ -2954,10 +2927,9 @@
         finishActivityResults(Activity.RESULT_CANCELED,
                 null /* resultData */, null /* resultGrants */);
         makeFinishingLocked();
-        if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE) {
-            Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + " from stack, reason="
-                    + reason + ", callers=" + Debug.getCallers(5));
-        }
+
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s from stack, reason= %s "
+                        + "callers=%s", this, reason, Debug.getCallers(5));
 
         takeFromHistory();
         removeTimeouts();
@@ -2997,7 +2969,7 @@
     void destroyed(String reason) {
         removeDestroyTimeout();
 
-        if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + this);
+        ProtoLog.d(WM_DEBUG_CONTAINERS, "activityDestroyedLocked: r=%s", this);
 
         if (!isState(DESTROYING, DESTROYED)) {
             throw new IllegalStateException(
@@ -3198,12 +3170,9 @@
             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));
-            }
+            ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s hasSavedState=%b "
+                    + "stateNotNeeded=%s finishing=%b state=%s callers=%s", this,
+                    mHaveState, stateNotNeeded, finishing, mState, 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),
@@ -4315,7 +4284,7 @@
         } else {
             // If we are being set visible, and the starting window is not yet displayed,
             // then make sure it doesn't get displayed.
-            if (startingWindow != null && !startingWindow.isDrawnLw()) {
+            if (startingWindow != null && !startingWindow.isDrawn()) {
                 startingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
                 startingWindow.mLegacyPolicyVisibilityAfterAnim = false;
             }
@@ -4497,16 +4466,40 @@
             detachChildren();
         }
 
-        if (state == RESUMED) {
-            mAtmService.updateBatteryStats(this, true);
-            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED);
-        } else if (state == PAUSED) {
-            mAtmService.updateBatteryStats(this, false);
-            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
-        } else if (state == STOPPED) {
-            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
-        } else if (state == DESTROYED) {
-            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+        switch (state) {
+            case RESUMED:
+                mAtmService.updateBatteryStats(this, true);
+                mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED);
+                // Fall through.
+            case STARTED:
+                // 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 */);
+                }
+                break;
+            case PAUSED:
+                mAtmService.updateBatteryStats(this, false);
+                mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
+                break;
+            case STOPPED:
+                mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+                break;
+            case DESTROYED:
+                mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+                // Fall through.
+            case DESTROYING:
+                if (app != null && !app.hasActivities()) {
+                    // Update any services we are bound to that might care about whether
+                    // their client may have activities.
+                    // No longer have activities, so update LRU list and oom adj.
+                    app.updateProcessInfo(true /* updateServiceConnectionActivities */,
+                            false /* activityChange */, true /* updateOomAdj */,
+                            false /* addPendingTopUid */);
+                }
+                break;
         }
     }
 
@@ -4674,7 +4667,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);
 
@@ -4816,6 +4809,7 @@
                 Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
             }
             setState(STARTED, "makeActiveIfNeeded");
+
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                         StartActivityItem.obtain());
@@ -5123,7 +5117,7 @@
             if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + this);
             setState(STOPPED, "stopIfPossible");
             if (deferRelaunchUntilPaused) {
-                destroyImmediately(true /* removeFromApp */, "stop-except");
+                destroyImmediately("stop-except");
             }
         }
     }
@@ -5164,7 +5158,7 @@
                 clearOptionsLocked();
             } else {
                 if (deferRelaunchUntilPaused) {
-                    destroyImmediately(true /* removeFromApp */, "stop-config");
+                    destroyImmediately("stop-config");
                     mRootWindowContainer.resumeFocusedStacksTopActivities();
                 } else {
                     mRootWindowContainer.updatePreviousProcess(this);
@@ -5404,11 +5398,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;
@@ -5514,7 +5504,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;
@@ -5522,9 +5512,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,
@@ -5538,6 +5530,10 @@
         }
     }
 
+    boolean isReportedDrawn() {
+        return mReportedDrawn;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
@@ -5588,9 +5584,9 @@
             if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
                 final boolean isAnimationSet = isAnimating(TRANSITION | PARENTS,
                         ANIMATION_TYPE_APP_TRANSITION);
-                Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
+                Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
                         + ", isAnimationSet=" + isAnimationSet);
-                if (!w.isDrawnLw()) {
+                if (!w.isDrawn()) {
                     Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
                             + " pv=" + w.isVisibleByPolicy()
                             + " mDrawState=" + winAnimator.drawStateToString()
@@ -5605,7 +5601,7 @@
                     if (findMainWindow(false /* includeStartingApp */) != w) {
                         mNumInterestingWindows++;
                     }
-                    if (w.isDrawnLw()) {
+                    if (w.isDrawn()) {
                         mNumDrawnWindows++;
 
                         if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
@@ -5618,7 +5614,7 @@
                         isInterestingAndDrawn = true;
                     }
                 }
-            } else if (w.isDrawnLw()) {
+            } else if (w.isDrawn()) {
                 // The starting window for this container is drawn.
                 mStackSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(this);
                 startingDisplayed = true;
@@ -6147,7 +6143,7 @@
         if (win == null) {
             return;
         }
-        final Rect frame = win.getRelativeFrameLw();
+        final Rect frame = win.getRelativeFrame();
         final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId
                 ? R.drawable.ic_account_circle
                 : R.drawable.ic_corp_badge;
@@ -6173,7 +6169,7 @@
         // destination of the thumbnail header animation. If this is a full screen
         // window scenario, we use the whole display as the target.
         WindowState win = findMainWindow();
-        Rect appRect = win != null ? win.getContentFrameLw() :
+        final Rect appRect = win != null ? win.getContentFrame() :
                 new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
         final Rect insets = win != null ? win.getContentInsets() : null;
         final Configuration displayConfig = mDisplayContent.getConfiguration();
@@ -6999,27 +6995,27 @@
             boolean ignoreVisibility) {
         final Task stack = getRootTask();
         if (stack.mConfigWillChange) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check (will change): " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
+                    + "(will change): %s", this);
             return true;
         }
 
         // We don't worry about activities that are finishing.
         if (finishing) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration doesn't matter in finishing " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter "
+                    + "in finishing %s", this);
             stopFreezingScreenLocked(false);
             return true;
         }
 
         if (!ignoreVisibility && (mState == STOPPING || mState == STOPPED || !shouldBeVisible())) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check invisible: " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
+                    + "invisible: %s", this);
             return true;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Ensuring correct configuration: " + this);
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
+                + "configuration: %s", this);
 
         final int newDisplayId = getDisplayId();
         final boolean displayChanged = mLastReportedDisplayId != newDisplayId;
@@ -7035,8 +7031,8 @@
         // the combine configurations are equal, but would otherwise differ in the override config
         mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
         if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration & display unchanged in " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+                    + "unchanged in %s", this);
             return true;
         }
 
@@ -7056,14 +7052,14 @@
             // No need to relaunch or schedule new config for activity that hasn't been launched
             // yet. We do, however, return after applying the config to activity record, so that
             // it will use it for launch transaction.
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check for initializing activity: " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check for "
+                    + "initializing activity: %s", this);
             return true;
         }
 
         if (changes == 0 && !forceNewConfig) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration no differences in " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
+                    this);
             // There are no significant differences, so we won't relaunch but should still deliver
             // the new configuration to the client process.
             if (displayChanged) {
@@ -7074,26 +7070,23 @@
             return true;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Configuration changes for " + this + ", allChanges="
-                        + Configuration.configurationDiffToString(changes));
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration changes for %s, "
+                + "allChanges=%s", this, Configuration.configurationDiffToString(changes));
 
         // If the activity isn't currently running, just leave the new configuration and it will
         // pick that up next time it starts.
         if (!attachedToProcess()) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Configuration doesn't matter not running " + this);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
             stopFreezingScreenLocked(false);
             forceNewConfig = false;
             return true;
         }
 
         // Figure out how to handle the changes between the configurations.
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                "Checking to restart " + info.name + ": changed=0x"
-                        + Integer.toHexString(changes) + ", handles=0x"
-                        + Integer.toHexString(info.getRealConfigChanged())
-                        + ", mLastReportedConfiguration=" + mLastReportedConfiguration);
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
+                + "handles=0x%s, mLastReportedConfiguration=%s", info.name,
+                Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
+                mLastReportedConfiguration);
 
         if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
@@ -7110,20 +7103,20 @@
                 mRelaunchReason = RELAUNCH_REASON_NONE;
             }
             if (!attachedToProcess()) {
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is destroying non-running " + this);
-                destroyImmediately(true /* removeFromApp */, "config");
+                ProtoLog.v(WM_DEBUG_CONFIGURATION,
+                        "Config is destroying non-running %s", this);
+                destroyImmediately("config");
             } else if (mState == PAUSING) {
                 // A little annoying: we are waiting for this activity to finish pausing. Let's not
                 // do anything now, but just flag that it needs to be restarted when done pausing.
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is skipping already pausing " + this);
+                ProtoLog.v(WM_DEBUG_CONFIGURATION,
+                        "Config is skipping already pausing %s", this);
                 deferRelaunchUntilPaused = true;
                 preserveWindowOnDeferredRelaunch = preserveWindow;
                 return true;
             } else {
-                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Config is relaunching " + this);
+                ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
+                        this);
                 if (DEBUG_STATES && !mVisibleRequested) {
                     Slog.v(TAG_STATES, "Config is relaunching invisible activity " + this
                             + " called by " + Debug.getCallers(4));
@@ -7677,7 +7670,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 2c475e0..34f7f79 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -41,10 +41,8 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.Process.INVALID_UID;
-import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.TYPE_VIRTUAL;
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
@@ -1869,7 +1867,7 @@
         for (int i = 0; i < numFinishingActivities; i++) {
             final ActivityRecord r = finishingActivities.get(i);
             if (r.isInHistory()) {
-                r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
+                r.destroyImmediately("finish-" + reason);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 15e88fc..be7a6ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -56,12 +56,12 @@
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
@@ -116,6 +116,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
 import com.android.server.power.ShutdownCheckPoints;
@@ -669,10 +670,8 @@
                 if (stack != null) {
                     stack.mConfigWillChange = globalConfigWillChange;
                 }
-                if (DEBUG_CONFIGURATION) {
-                    Slog.v(TAG_CONFIGURATION, "Starting activity when config will change = "
-                            + globalConfigWillChange);
-                }
+                ProtoLog.v(WM_DEBUG_CONFIGURATION, "Starting activity when config "
+                        + "will change = %b", globalConfigWillChange);
 
                 final long origId = Binder.clearCallingIdentity();
 
@@ -695,10 +694,9 @@
                     if (stack != null) {
                         stack.mConfigWillChange = false;
                     }
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.v(TAG_CONFIGURATION,
+                    ProtoLog.v(WM_DEBUG_CONFIGURATION,
                                 "Updating to new configuration after starting activity.");
-                    }
+
                     mService.updateConfigurationLocked(mRequest.globalConfig, null, false);
                 }
 
@@ -1693,8 +1691,9 @@
             // we need to resolve resultTo to a uid as grantImplicitAccess deals explicitly in UIDs
             final PackageManagerInternal pmInternal =
                     mService.getPackageManagerInternalLocked();
-            final int resultToUid = pmInternal.getPackageUidInternal(
-                            mStartActivity.resultTo.info.packageName, 0, mStartActivity.mUserId);
+            final int resultToUid = pmInternal.getPackageUid(
+                    mStartActivity.resultTo.info.packageName, 0 /* flags */,
+                    mStartActivity.mUserId);
             pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
                     UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
                     resultToUid /*visible*/, true /*direct*/);
@@ -1766,8 +1765,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 +1869,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 +1916,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;
         }
@@ -2515,8 +2501,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/ActivityTaskManagerDebugConfig.java b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
index da0bfd6..3c562a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
@@ -43,12 +43,6 @@
     // Enable all debug log categories for activities.
     private static final boolean DEBUG_ALL_ACTIVITIES = DEBUG_ALL || false;
 
-    static final boolean DEBUG_ADD_REMOVE = DEBUG_ALL_ACTIVITIES || false;
-    public static final boolean DEBUG_CONFIGURATION = DEBUG_ALL || false;
-    static final boolean DEBUG_CONTAINERS = DEBUG_ALL_ACTIVITIES || false;
-    static final boolean DEBUG_FOCUS = false;
-    static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
-    static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8204b36..6a8cbfb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -66,6 +66,10 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
@@ -92,10 +96,6 @@
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IMMERSIVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
@@ -242,6 +242,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.KeyguardDismissCallback;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
@@ -251,6 +252,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;
@@ -812,7 +814,7 @@
             // in-place.
             updateConfigurationLocked(configuration, null, true);
             final Configuration globalConfig = getGlobalConfiguration();
-            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Initial config: " + globalConfig);
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Initial config: %s", globalConfig);
 
             // Load resources only after the current configuration has been set.
             final Resources res = mContext.getResources();
@@ -1959,7 +1961,7 @@
 
             // update associated state if we're frontmost
             if (r.isFocusedActivityOnDisplay()) {
-                if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE, "Frontmost changed immersion: "+ r);
+                ProtoLog.d(WM_DEBUG_IMMERSIVE, "Frontmost changed immersion: %s", r);
                 applyUpdateLockStateLocked(r);
             }
         }
@@ -1973,8 +1975,8 @@
         final boolean nextState = r != null && r.immersive;
         mH.post(() -> {
             if (mUpdateLock.isHeld() != nextState) {
-                if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE,
-                        "Applying new update lock state '" + nextState + "' for " + r);
+                ProtoLog.d(WM_DEBUG_IMMERSIVE, "Applying new update lock state '%s' for %s",
+                        nextState, r);
                 if (nextState) {
                     mUpdateLock.acquire();
                 } else {
@@ -2175,7 +2177,7 @@
     @Override
     public void setFocusedStack(int stackId) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedStack()");
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedStack: stackId=" + stackId);
+        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedStack: stackId=%d", stackId);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2197,7 +2199,7 @@
     @Override
     public void setFocusedTask(int taskId) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedTask()");
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId);
+        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d", taskId);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -3012,7 +3014,7 @@
     }
 
     private void startLockTaskModeLocked(@Nullable Task task, boolean isSystemCaller) {
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskModeLocked: %s", task);
         if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
             return;
         }
@@ -3074,8 +3076,7 @@
                     "updateLockTaskPackages()");
         }
         synchronized (mGlobalLock) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowlisting " + userId + ":"
-                    + Arrays.toString(packages));
+            ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId, Arrays.toString(packages));
             getLockTaskController().updateLockTaskPackages(userId, packages);
         }
     }
@@ -3378,7 +3379,7 @@
                 if (r == null || !r.isDestroyable()) {
                     return false;
                 }
-                r.destroyImmediately(true /* removeFromApp */, "app-req");
+                r.destroyImmediately("app-req");
                 return r.isState(DESTROYING, DESTROYED);
             } finally {
                 Binder.restoreCallingIdentity(origId);
@@ -4000,9 +4001,9 @@
     @Override
     public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
             int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
-        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
-                + Arrays.toString(horizontalSizeConfiguration) + " "
-                + Arrays.toString(verticalSizeConfigurations));
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Report configuration: %s %s %s",
+                token, Arrays.toString(horizontalSizeConfiguration),
+                Arrays.toString(verticalSizeConfigurations));
         synchronized (mGlobalLock) {
             ActivityRecord record = ActivityRecord.isInStackLocked(token);
             if (record == null) {
@@ -4496,8 +4497,8 @@
                     "updateLockTaskFeatures()");
         }
         synchronized (mGlobalLock) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" +
-                    Integer.toHexString(flags));
+            ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowing features %d:0x%s",
+                    userId, Integer.toHexString(flags));
             getLockTaskController().updateLockTaskFeatures(userId, flags);
         }
     }
@@ -5182,8 +5183,8 @@
             return 0;
         }
 
-        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
-                "Updating global configuration to: " + values);
+        ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
+                + "to: %s", values);
         writeConfigurationChanged(changes);
         FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
                 values.colorMode,
@@ -5261,10 +5262,8 @@
         for (int i = pidMap.size() - 1; i >= 0; i--) {
             final int pid = pidMap.keyAt(i);
             final WindowProcessController app = pidMap.get(pid);
-            if (DEBUG_CONFIGURATION) {
-                Slog.v(TAG_CONFIGURATION, "Update process config of "
-                        + app.mName + " to new config " + configCopy);
-            }
+            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
+                    + "config %s", app.mName, configCopy);
             app.onConfigurationChanged(configCopy);
         }
 
@@ -6562,10 +6561,8 @@
             if (InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED) return;
 
             if (pid == MY_PID || pid < 0) {
-                if (DEBUG_CONFIGURATION) {
-                    Slog.w(TAG,
+                ProtoLog.w(WM_DEBUG_CONFIGURATION,
                             "Trying to update display configuration for system/invalid process.");
-                }
                 return;
             }
             synchronized (mGlobalLock) {
@@ -6573,18 +6570,14 @@
                         mRootWindowContainer.getDisplayContent(displayId);
                 if (displayContent == null) {
                     // Call might come when display is not yet added or has been removed.
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.w(TAG, "Trying to update display configuration for non-existing "
-                                + "displayId=" + displayId);
-                    }
+                    ProtoLog.w(WM_DEBUG_CONFIGURATION, "Trying to update display "
+                            + "configuration for non-existing displayId=%d", displayId);
                     return;
                 }
                 final WindowProcessController process = mProcessMap.getProcess(pid);
                 if (process == null) {
-                    if (DEBUG_CONFIGURATION) {
-                        Slog.w(TAG, "Trying to update display configuration for invalid "
-                                + "process, pid=" + pid);
-                    }
+                    ProtoLog.w(WM_DEBUG_CONFIGURATION, "Trying to update display "
+                            + "configuration for invalid process, pid=%d", pid);
                     return;
                 }
                 process.registerDisplayConfigurationListener(displayContent);
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index c144755..8568d5f 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -222,12 +222,12 @@
     }
 
     protected boolean skipAnimation() {
-        return !mWin.isDrawnLw();
+        return !mWin.isDrawn();
     }
 
     private @StatusBarManager.WindowVisibleState int computeStateLw(
             boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
-        if (win.isDrawnLw()) {
+        if (win.isDrawn()) {
             final boolean vis = win.isVisibleLw();
             final boolean anim = win.isAnimatingLw();
             if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
@@ -264,7 +264,7 @@
     }
 
     boolean checkHiddenLw() {
-        if (mWin != null && mWin.isDrawnLw()) {
+        if (mWin != null && mWin.isDrawn()) {
             if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
                 updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
             }
@@ -291,7 +291,7 @@
         } else if (mWin == null) {
             if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
             return false;
-        } else if (mWin.isDisplayedLw()) {
+        } else if (mWin.isDisplayed()) {
             if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
             return false;
         } else {
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 167afab..7e55f0a 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -37,6 +37,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FastXmlSerializer;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -333,8 +334,8 @@
                 }
                 try {
                     if (app.hasThread()) {
-                        if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
-                                + app.mName + " new compat " + ci);
+                        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s "
+                                + "new compat %s", app.mName, ci);
                         app.getThread().updatePackageCompatibilityInfo(packageName, ci);
                     }
                 } catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index 9a397fe..01c007e 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -18,6 +18,9 @@
 
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -25,6 +28,8 @@
 import android.window.IDisplayAreaOrganizer;
 import android.window.IDisplayAreaOrganizerController;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 import java.util.HashMap;
 
 public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerController.Stub {
@@ -67,9 +72,12 @@
     @Override
     public void registerOrganizer(IDisplayAreaOrganizer organizer, int feature) {
         enforceStackPermission("registerOrganizer()");
+        final long uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register display organizer=%s uid=%d",
+                        organizer.asBinder(), uid);
                 if (mOrganizersByFeatureIds.get(feature) != null) {
                     throw new IllegalStateException(
                             "Replacing existing organizer currently unsupported");
@@ -96,9 +104,12 @@
     @Override
     public void unregisterOrganizer(IDisplayAreaOrganizer organizer) {
         enforceStackPermission("unregisterTaskOrganizer()");
+        final long uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Unregister display organizer=%s uid=%d",
+                        organizer.asBinder(), uid);
                 mOrganizersByFeatureIds.entrySet().removeIf(
                         entry -> entry.getValue().asBinder() == organizer.asBinder());
 
@@ -113,6 +124,7 @@
     }
 
     void onDisplayAreaAppeared(IDisplayAreaOrganizer organizer, DisplayArea da) {
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "DisplayArea appeared name=%s", da.getName());
         try {
             SurfaceControl outSurfaceControl = new SurfaceControl(da.getSurfaceControl(),
                     "DisplayAreaOrganizerController.onDisplayAreaAppeared");
@@ -123,6 +135,7 @@
     }
 
     void onDisplayAreaVanished(IDisplayAreaOrganizer organizer, DisplayArea da) {
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "DisplayArea vanished name=%s", da.getName());
         try {
             organizer.onDisplayAreaVanished(da.getDisplayAreaInfo());
         } catch (RemoteException e) {
@@ -131,6 +144,7 @@
     }
 
     void onDisplayAreaInfoChanged(IDisplayAreaOrganizer organizer, DisplayArea da) {
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "DisplayArea info changed name=%s", da.getName());
         try {
             organizer.onDisplayAreaInfoChanged(da.getDisplayAreaInfo());
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index affbafa..2f7cc69 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -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;
@@ -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.
@@ -701,7 +694,7 @@
         // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
         // wasting time and funky changes while a window is animating away.
         final boolean gone = (mTmpWindow != null && mWmService.mPolicy.canBeHiddenByKeyguardLw(w))
-                || w.isGoneForLayoutLw();
+                || w.isGoneForLayout();
 
         if (DEBUG_LAYOUT && !w.mLayoutAttached) {
             Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
@@ -749,7 +742,7 @@
             if (firstLayout) {
                 // The client may compute its actual requested size according to the first layout,
                 // so we still request the window to resize if the current frame is empty.
-                if (!w.getFrameLw().isEmpty()) {
+                if (!w.getFrame().isEmpty()) {
                     w.updateLastFrames();
                 }
                 w.updateLastInsetValues();
@@ -760,9 +753,9 @@
                 w.mActivityRecord.layoutLetterbox(w);
             }
 
-            if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrameLw()
+            if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrame()
                     + " mContainingFrame=" + w.getContainingFrame()
-                    + " mDisplayFrame=" + w.getDisplayFrameLw());
+                    + " mDisplayFrame=" + w.getDisplayFrame());
         }
     };
 
@@ -787,9 +780,9 @@
                 w.prelayout();
                 getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
                 w.mLayoutSeq = mLayoutSeq;
-                if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrameLw()
+                if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
                         + " mContainingFrame=" + w.getContainingFrame()
-                        + " mDisplayFrame=" + w.getDisplayFrameLw());
+                        + " mDisplayFrame=" + w.getDisplayFrame());
             }
         }
     };
@@ -814,7 +807,7 @@
         w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
 
         if (!mTmpApplySurfaceChangesTransactionState.obscured) {
-            final boolean isDisplayed = w.isDisplayedLw();
+            final boolean isDisplayed = w.isDisplayed();
 
             if (isDisplayed && w.isObscuringDisplay()) {
                 // This window completely covers everything behind it, so we want to leave all
@@ -899,10 +892,6 @@
             }
         }
 
-        if (!mLosingFocus.isEmpty() && w.isFocused() && w.isDisplayedLw()) {
-            mWmService.mH.obtainMessage(REPORT_LOSING_FOCUS, this).sendToTarget();
-        }
-
         w.updateResizingWindowIfNeeded();
     };
 
@@ -2560,7 +2549,7 @@
                 return;
             }
 
-            if (w.isOnScreen() && w.isVisibleLw() && w.getFrameLw().contains(x, y)) {
+            if (w.isOnScreen() && w.isVisibleLw() && w.getFrame().contains(x, y)) {
                 targetWindowType[0] = w.mAttrs.type;
                 return;
             }
@@ -2758,7 +2747,7 @@
     void adjustForImeIfNeeded() {
         final WindowState imeWin = mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw()
-                && imeWin.isDisplayedLw();
+                && imeWin.isDisplayed();
         final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
         mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
     }
@@ -2924,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");
@@ -3157,7 +3131,6 @@
                 mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
-        mLosingFocus.remove(newFocus);
 
         if (newFocus != null) {
             mWinAddedSinceNullFocus.clear();
@@ -3391,7 +3364,7 @@
         // Now, a special case -- if the last target's window is in the process of exiting, but
         // not removed, keep on the last target to avoid IME flicker. The exception is if the
         // current target is home since we want opening apps to become the IME target right away.
-        if (curTarget != null && !curTarget.mRemoved && curTarget.isDisplayedLw()
+        if (curTarget != null && !curTarget.mRemoved && curTarget.isDisplayed()
                 && curTarget.isClosing() && !curTarget.isActivityTypeHome()) {
             if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Not changing target till current window is"
                     + " closing and not removed");
@@ -3674,7 +3647,7 @@
 
         final WindowState visibleNotDrawnWindow = getWindow(w -> {
             final boolean isVisible = w.isVisible() && !w.mObscured;
-            final boolean isDrawn = w.isDrawnLw();
+            final boolean isDrawn = w.isDrawn();
             if (isVisible && !isDrawn) {
                 ProtoLog.d(WM_DEBUG_BOOT,
                         "DisplayContent: boot is waiting for window of type %d to be drawn",
@@ -4009,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;
@@ -4629,9 +4599,9 @@
             DisplayContent dc = this;
             do {
                 final WindowState displayParent = dc.getParentWindow();
-                location.x += displayParent.getFrameLw().left
+                location.x += displayParent.getFrame().left
                         + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
-                location.y += displayParent.getFrameLw().top
+                location.y += displayParent.getFrame().top
                         + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
                 dc = displayParent.getDisplayContent();
             } while (dc != null && dc.getParentWindow() != null);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2e03cb8..1c147c2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -195,13 +195,13 @@
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowManagerPolicy.InputConsumer;
 import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.policy.WindowOrientationListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
@@ -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;
@@ -413,7 +416,7 @@
     private boolean mAllowLockscreenWhenOn;
 
     @VisibleForTesting
-    InputConsumer mInputConsumer = null;
+    EventReceiverInputConsumer mInputConsumer;
 
     private PointerLocationView mPointerLocationView;
 
@@ -459,7 +462,7 @@
                     }
                     break;
                 case MSG_DISPOSE_INPUT_CONSUMER:
-                    disposeInputConsumer((InputConsumer) msg.obj);
+                    disposeInputConsumer((EventReceiverInputConsumer) msg.obj);
                     break;
                 case MSG_ENABLE_POINTER_LOCATION:
                     enablePointerLocation();
@@ -1123,7 +1126,7 @@
 
                         // For IME we use regular frame.
                         (displayFrames, windowState, inOutFrame) ->
-                                inOutFrame.set(windowState.getFrameLw()));
+                                inOutFrame.set(windowState.getFrame()));
 
                 mDisplayContent.setInsetProvider(ITYPE_BOTTOM_GESTURES, win,
                         (displayFrames, windowState, inOutFrame) -> {
@@ -1200,11 +1203,11 @@
                 // IME should not provide frame which is smaller than the nav bar frame. Otherwise,
                 // nav bar might be overlapped with the content of the client when IME is shown.
                 sTmpRect.set(inOutFrame);
-                sTmpRect.intersectUnchecked(mNavigationBar.getFrameLw());
-                inOutFrame.inset(windowState.getGivenContentInsetsLw());
+                sTmpRect.intersectUnchecked(mNavigationBar.getFrame());
+                inOutFrame.inset(windowState.mGivenContentInsets);
                 inOutFrame.union(sTmpRect);
             } else {
-                inOutFrame.inset(windowState.getGivenContentInsetsLw());
+                inOutFrame.inset(windowState.mGivenContentInsets);
             }
         };
     }
@@ -2072,7 +2075,7 @@
 
             // In case we forced the window to draw behind the navigation bar, restrict df to
             // DF.Restricted to simulate old compat behavior.
-            Rect parentDisplayFrame = attached.getDisplayFrameLw();
+            Rect parentDisplayFrame = attached.getDisplayFrame();
             final WindowManager.LayoutParams attachedAttrs = attached.mAttrs;
             if ((attachedAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
                     && (attachedAttrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
@@ -2093,14 +2096,14 @@
                 // setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag.
                 // Otherwise, use the overscan frame.
                 cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
-                        ? attached.getContentFrameLw() : parentDisplayFrame);
+                        ? attached.getContentFrame() : parentDisplayFrame);
             } else {
                 // If the window is resizing, then we want to base the content frame on our attached
                 // content frame to resize...however, things can be tricky if the attached window is
                 // NOT in resize mode, in which case its content frame will be larger.
                 // Ungh. So to deal with that, make sure the content frame we end up using is not
                 // covering the IM dock.
-                cf.set(attached.getContentFrameLw());
+                cf.set(attached.getContentFrame());
                 if (attached.isVoiceInteraction()) {
                     cf.intersectUnchecked(displayFrames.mVoiceContent);
                 } else if (win.isInputMethodTarget() || attached.isInputMethodTarget()) {
@@ -2108,11 +2111,11 @@
                 }
             }
             df.set(insetDecors ? parentDisplayFrame : cf);
-            vf.set(attached.getVisibleFrameLw());
+            vf.set(attached.getVisibleFrame());
         }
         // The LAYOUT_IN_SCREEN flag is used to determine whether the attached window should be
         // positioned relative to its parent or the entire screen.
-        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
+        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
     }
 
     private void applyStableConstraints(int sysui, int fl, Rect r, DisplayFrames displayFrames) {
@@ -2214,8 +2217,8 @@
                 vf.set(adjust != SOFT_INPUT_ADJUST_NOTHING
                         ? displayFrames.mCurrent : displayFrames.mDock);
             } else {
-                pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
-                vf.set(attached.getVisibleFrameLw());
+                pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
+                vf.set(attached.getVisibleFrame());
             }
             cf.set(adjust != SOFT_INPUT_ADJUST_RESIZE
                     ? displayFrames.mDock : displayFrames.mContent);
@@ -2620,12 +2623,10 @@
         win.computeFrame(displayFrames);
         // Dock windows carve out the bottom of the screen, so normal windows
         // can't appear underneath them.
-        if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
-                && !win.getGivenInsetsPendingLw()) {
+        if (type == TYPE_INPUT_METHOD && win.isVisibleLw() && !win.mGivenInsetsPending) {
             offsetInputMethodWindowLw(win, displayFrames);
         }
-        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
-                && !win.getGivenInsetsPendingLw()) {
+        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw() && !win.mGivenInsetsPending) {
             offsetVoiceInputWindowLw(win, displayFrames);
         }
     }
@@ -2642,8 +2643,8 @@
         final int navBarPosition = navigationBarPosition(displayFrames.mDisplayWidth,
                 displayFrames.mDisplayHeight, rotation);
 
-        int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
-        top += win.getGivenContentInsetsLw().top;
+        int top = Math.max(win.getDisplayFrame().top, win.getContentFrame().top);
+        top += win.mGivenContentInsets.top;
         displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top);
         if (navBarPosition == NAV_BAR_BOTTOM) {
             // Always account for the nav bar frame height on the bottom since in all navigation
@@ -2655,8 +2656,8 @@
                     displayFrames.mUnrestricted.bottom - navFrameHeight);
         }
         displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
-        top = win.getVisibleFrameLw().top;
-        top += win.getGivenVisibleInsetsLw().top;
+        top = win.getVisibleFrame().top;
+        top += win.mGivenVisibleInsets.top;
         displayFrames.mCurrent.bottom = Math.min(displayFrames.mCurrent.bottom, top);
         if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
                 + displayFrames.mDock.bottom + " mContentBottom="
@@ -2664,8 +2665,8 @@
     }
 
     private void offsetVoiceInputWindowLw(WindowState win, DisplayFrames displayFrames) {
-        int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
-        top += win.getGivenContentInsetsLw().top;
+        int top = Math.max(win.getDisplayFrame().top, win.getContentFrame().top);
+        top += win.mGivenContentInsets.top;
         displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
     }
 
@@ -2726,8 +2727,7 @@
             if (win.isDreamWindow()) {
                 // If the lockscreen was showing when the dream started then wait
                 // for the dream to draw before hiding the lockscreen.
-                if (!mDreamingLockscreen
-                        || (win.isVisibleLw() && win.hasDrawnLw())) {
+                if (!mDreamingLockscreen || (win.isVisibleLw() && win.hasDrawn())) {
                     mShowingDream = true;
                     appWindow = true;
                 }
@@ -2913,7 +2913,7 @@
         final InsetsSource request = mTopFullscreenOpaqueWindowState.getRequestedInsetsState()
                 .peekSource(ITYPE_STATUS_BAR);
         if (WindowManagerDebugConfig.DEBUG) {
-            Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
+            Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrame());
             Slog.d(TAG, "attr: " + attrs + " request: " + request);
         }
         return (fl & LayoutParams.FLAG_FULLSCREEN) != 0
@@ -3353,8 +3353,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 +3369,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();
@@ -3389,7 +3395,7 @@
         mImmersiveModeConfirmation.confirmCurrentPrompt();
     }
 
-    private void disposeInputConsumer(InputConsumer inputConsumer) {
+    private void disposeInputConsumer(EventReceiverInputConsumer inputConsumer) {
         if (inputConsumer != null) {
             inputConsumer.dispose();
         }
@@ -3770,8 +3776,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);
             }
         }
 
@@ -4163,6 +4168,6 @@
             return false;
         }
 
-        return Rect.intersects(targetWindow.getFrameLw(), navBarWindow.getFrameLw());
+        return Rect.intersects(targetWindow.getFrame(), navBarWindow.getFrame());
     }
 }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 133b111..c9f463b 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -283,8 +283,9 @@
             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
             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 86e2698..f0f3385 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -93,7 +93,7 @@
                 || (mImeTargetFromIme != null
                 && isImeTargetFromDisplayContentAndImeSame()
                 && mWin != null
-                && mWin.isDrawnLw()
+                && mWin.isDrawn()
                 && !mWin.mGivenInsetsPending)) {
             mIsImeLayoutDrawn = true;
             // show IME if InputMethodService requested it to be shown.
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 3b24584b..a79d3bb 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -80,8 +80,7 @@
         mWindowHandle.layoutParamsFlags = 0;
         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/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0fe9735..fb511e0 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -61,7 +61,6 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
 import java.util.Set;
@@ -95,8 +94,11 @@
      */
     private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap();
 
-    private static final class EventReceiverInputConsumer extends InputConsumerImpl
-            implements WindowManagerPolicy.InputConsumer {
+    /**
+     * Representation of a input consumer that the policy has added to the window manager to consume
+     * input events going to windows below it.
+     */
+    static final class EventReceiverInputConsumer extends InputConsumerImpl {
         private InputMonitor mInputMonitor;
         private final InputEventReceiver mInputEventReceiver;
 
@@ -111,8 +113,8 @@
                     mClientChannel, looper);
         }
 
-        @Override
-        public void dismiss() {
+        /** Removes the input consumer from the window manager. */
+        void dismiss() {
             synchronized (mService.mGlobalLock) {
                 mInputMonitor.mInputConsumers.remove(mName);
                 hide(mInputMonitor.mInputTransaction);
@@ -120,8 +122,8 @@
             }
         }
 
-        @Override
-        public void dispose() {
+        /** Disposes the input consumer and input receiver from the associated thread. */
+        void dispose() {
             synchronized (mService.mGlobalLock) {
                 disposeChannelsLw(mInputMonitor.mInputTransaction);
                 mInputEventReceiver.dispose();
@@ -225,8 +227,13 @@
         }
     }
 
-    WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
+    EventReceiverInputConsumer 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 +263,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);
     }
@@ -271,8 +283,7 @@
         inputWindowHandle.layoutParamsType = type;
         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;
@@ -280,7 +291,7 @@
         inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
         inputWindowHandle.displayId = child.getDisplayId();
 
-        final Rect frame = child.getFrameLw();
+        final Rect frame = child.getFrame();
         inputWindowHandle.frameLeft = frame.left;
         inputWindowHandle.frameTop = frame.top;
         inputWindowHandle.frameRight = frame.right;
@@ -463,9 +474,6 @@
             mDisplayContent.forAllWindows(this,
                     true /* traverseTopToBottom */);
 
-            if (mAddWallpaperInputConsumerHandle) {
-                mWallpaperInputConsumer.show(mInputTransaction, 0);
-            }
             if (!mUpdateInputWindowsImmediately) {
                 mDisplayContent.getPendingTransaction().merge(mInputTransaction);
                 mDisplayContent.scheduleAnimation();
@@ -572,8 +580,7 @@
         inputWindowHandle.layoutParamsType = type;
         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 d1eb795..e00c9e7 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -173,7 +173,7 @@
         // frame may not yet determined that server side doesn't think the window is ready to
         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
         if (mServerVisible) {
-            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.set(mWin.getFrame());
             if (mFrameProvider != null) {
                 mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect);
             } else {
@@ -185,14 +185,14 @@
         mSource.setFrame(mTmpRect);
 
         if (mImeFrameProvider != null) {
-            mImeOverrideFrame.set(mWin.getFrameLw());
+            mImeOverrideFrame.set(mWin.getFrame());
             mImeFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin,
                     mImeOverrideFrame);
         }
 
         if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
                 || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
-            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.set(mWin.getFrame());
             mTmpRect.inset(mWin.mGivenVisibleInsets);
             mSource.setVisibleFrame(mTmpRect);
         } else {
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 8ef57f7..c8d7693 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -29,7 +29,7 @@
 import static android.os.UserHandle.USER_CURRENT;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
 
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 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;
@@ -65,6 +65,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
@@ -448,7 +449,7 @@
      * unlike {@link #stopLockTaskMode(Task, boolean, int)}, it doesn't perform the checks.
      */
     void clearLockedTasks(String reason) {
-        if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+        ProtoLog.i(WM_DEBUG_LOCKTASK, "clearLockedTasks: %s", reason);
         if (!mLockTaskModeTasks.isEmpty()) {
             clearLockedTask(mLockTaskModeTasks.get(0));
         }
@@ -490,10 +491,10 @@
         if (!mLockTaskModeTasks.remove(task)) {
             return;
         }
-        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+        ProtoLog.d(WM_DEBUG_LOCKTASK, "removeLockedTask: removed %s", task);
         if (mLockTaskModeTasks.isEmpty()) {
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
-                    " last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "removeLockedTask: task=%s last task, "
+                    + "reverting locktask mode. Callers=%s", task, Debug.getCallers(3));
             mHandler.post(() -> performStopLockTask(task.mUserId));
         }
     }
@@ -558,7 +559,7 @@
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
                 // 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");
+                ProtoLog.w(WM_DEBUG_LOCKTASK, "Mode default, asking user");
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
                         StatusBarManagerInternal.class);
                 if (statusBarManager != null) {
@@ -569,8 +570,7 @@
         }
 
         // System can only initiate screen pinning, not full lock task mode
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                isSystemCaller ? "Locking pinned" : "Locking fully");
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "%s", isSystemCaller ? "Locking pinned" : "Locking fully");
         setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
                 "startLockTask", true);
     }
@@ -584,7 +584,7 @@
                                  String reason, boolean andResume) {
         // Should have already been checked, but do it again.
         if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+            ProtoLog.w(WM_DEBUG_LOCKTASK,
                     "setLockTaskMode: Can't lock due to auth");
             return;
         }
@@ -602,8 +602,8 @@
                     task.mUserId,
                     lockTaskModeState));
         }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
-                " Callers=" + Debug.getCallers(4));
+        ProtoLog.w(WM_DEBUG_LOCKTASK, "setLockTaskMode: Locking to %s Callers=%s",
+                task, Debug.getCallers(4));
 
         if (!mLockTaskModeTasks.contains(task)) {
             mLockTaskModeTasks.add(task);
@@ -672,8 +672,8 @@
             }
 
             // Terminate locked tasks that have recently lost allowlist authorization.
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
-                    lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "onLockTaskPackagesUpdated: removing %s"
+                    + " mLockTaskAuth()=%s", lockedTask, lockedTask.lockTaskAuthToString());
             removeLockedTask(lockedTask);
             lockedTask.performClearTaskLocked();
             taskChanged = true;
@@ -686,8 +686,8 @@
         if (mLockTaskModeTasks.isEmpty() && task!= null
                 && task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
             // This task must have just been authorized.
-            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
-                    "onLockTaskPackagesUpdated: starting new locktask task=" + task);
+            ProtoLog.d(WM_DEBUG_LOCKTASK, "onLockTaskPackagesUpdated: starting new "
+                    + "locktask task=%s", task);
             setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false);
             taskChanged = true;
         }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 143fbb0..6882dc4 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -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/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index cc5ed36..c3953b4 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -16,9 +16,8 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.Task.TAG_ADD_REMOVE;
 import static com.android.server.wm.Task.TAG_TASKS;
 
 import android.app.ActivityOptions;
@@ -27,6 +26,7 @@
 import android.os.Debug;
 import android.util.Slog;
 
+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;
@@ -218,8 +218,8 @@
             if (takeOptions) {
                 noOptions = takeOption(p, noOptions);
             }
-            if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing activity " + p + " from task="
-                    + mTask + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4));
+            ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s from task=%s "
+                    + "adding to task=%s Callers=%s", p, mTask,  targetTask, Debug.getCallers(4));
             if (DEBUG_TASKS) Slog.v(TAG_TASKS,
                     "Pushing next activity " + p + " out to target's task " + target);
             p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fe2d08f..21e30ce 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1106,14 +1106,14 @@
         final WindowManager.LayoutParams attrs = w.mAttrs;
         final int attrFlags = attrs.flags;
         final boolean onScreen = w.isOnScreen();
-        final boolean canBeSeen = w.isDisplayedLw();
+        final boolean canBeSeen = w.isDisplayed();
         final int privateflags = attrs.privateFlags;
         boolean displayHasContent = false;
 
         ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
                 "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w"
                         + ".isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
-                w, w.mHasSurface, onScreen, w.isDisplayedLw(), w.mAttrs.userActivityTimeout);
+                w, w.mHasSurface, onScreen, w.isDisplayed(), w.mAttrs.userActivityTimeout);
         if (w.mHasSurface && onScreen) {
             if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) {
                 mUserActivityTimeout = w.mAttrs.userActivityTimeout;
@@ -2716,7 +2716,7 @@
                 + " resumed=" + r.getStack().mResumedActivity + " pausing="
                 + r.getStack().mPausingActivity + " for reason " + mDestroyAllActivitiesReason);
 
-        r.destroyImmediately(true /* removeFromTask */, mDestroyAllActivitiesReason);
+        r.destroyImmediately(mDestroyAllActivitiesReason);
     }
 
     // Tries to put all activity stacks to sleep. Returns true if all stacks were
@@ -2816,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,
@@ -2965,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();
             }
@@ -3127,7 +3124,6 @@
     FinishDisabledPackageActivitiesHelper mFinishDisabledPackageActivitiesHelper =
             new FinishDisabledPackageActivitiesHelper();
     class FinishDisabledPackageActivitiesHelper {
-        private boolean mDidSomething;
         private String mPackageName;
         private Set<String> mFilterByClasses;
         private boolean mDoit;
@@ -3135,11 +3131,10 @@
         private int mUserId;
         private boolean mOnlyRemoveNoProcess;
         private Task mLastTask;
-        private ComponentName mHomeActivity;
+        private final ArrayList<ActivityRecord> mCollectedActivities = new ArrayList<>();
 
         private void reset(String packageName, Set<String> filterByClasses,
                 boolean doit, boolean evenPersistent, int userId, boolean onlyRemoveNoProcess) {
-            mDidSomething = false;
             mPackageName = packageName;
             mFilterByClasses = filterByClasses;
             mDoit = doit;
@@ -3147,7 +3142,6 @@
             mUserId = userId;
             mOnlyRemoveNoProcess = onlyRemoveNoProcess;
             mLastTask = null;
-            mHomeActivity = null;
         }
 
         boolean process(String packageName, Set<String> filterByClasses,
@@ -3155,14 +3149,35 @@
             reset(packageName, filterByClasses, doit, evenPersistent, userId, onlyRemoveNoProcess);
 
             final PooledFunction f = PooledLambda.obtainFunction(
-                    FinishDisabledPackageActivitiesHelper::processActivity, this,
+                    FinishDisabledPackageActivitiesHelper::collectActivity, this,
                     PooledLambda.__(ActivityRecord.class));
             forAllActivities(f);
             f.recycle();
-            return mDidSomething;
+
+            boolean didSomething = false;
+            final int size = mCollectedActivities.size();
+            // Keep the finishing order from top to bottom.
+            for (int i = 0; i < size; i++) {
+                final ActivityRecord r = mCollectedActivities.get(i);
+                if (mOnlyRemoveNoProcess) {
+                    if (!r.hasProcess()) {
+                        didSomething = true;
+                        Slog.i(TAG, "  Force removing " + r);
+                        r.cleanUp(false /* cleanServices */, false /* setState */);
+                        r.removeFromHistory("force-stop");
+                    }
+                } else {
+                    didSomething = true;
+                    Slog.i(TAG, "  Force finishing " + r);
+                    r.finishIfPossible("force-stop", true /* oomAdj */);
+                }
+            }
+            mCollectedActivities.clear();
+
+            return didSomething;
         }
 
-        private boolean processActivity(ActivityRecord r) {
+        private boolean collectActivity(ActivityRecord r) {
             final boolean sameComponent =
                     (r.packageName.equals(mPackageName) && (mFilterByClasses == null
                             || mFilterByClasses.contains(r.mActivityComponent.getClassName())))
@@ -3179,26 +3194,7 @@
                     }
                     return true;
                 }
-                if (r.isActivityTypeHome()) {
-                    if (mHomeActivity != null && mHomeActivity.equals(r.mActivityComponent)) {
-                        Slog.i(TAG, "Skip force-stop again " + r);
-                        return false;
-                    } else {
-                        mHomeActivity = r.mActivityComponent;
-                    }
-                }
-                if (mOnlyRemoveNoProcess) {
-                    if (noProcess) {
-                        mDidSomething = true;
-                        Slog.i(TAG, "  Force removing " + r);
-                        r.cleanUp(false /* cleanServices */, false /* setState */);
-                        r.removeFromHistory("force-stop");
-                    }
-                } else {
-                    mDidSomething = true;
-                    Slog.i(TAG, "  Force finishing " + r);
-                    r.finishIfPossible("force-stop", true /* oomAdj */);
-                }
+                mCollectedActivities.add(r);
                 mLastTask = r.getTask();
             }
 
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 6cf9432..7b5b0ad 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -99,9 +99,8 @@
                 // the task's profile
                 return;
             }
-            if (!mAllowed && !task.isActivityTypeHome()) {
-                // Skip if the caller isn't allowed to fetch this task, except for the home
-                // task which we always return.
+            if (!mAllowed) {
+                // Skip if the caller isn't allowed to fetch this task
                 return;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 3c8036d..25732e7 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -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/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 33935d6..7df2b40 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -26,6 +26,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.DebugUtils;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
@@ -429,7 +430,8 @@
 
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
-        pw.print(" mAnimationType=" + mAnimationType);
+        pw.print(" mAnimationType=" + DebugUtils.valueToString(SurfaceAnimator.class,
+                "ANIMATION_TYPE_", mAnimationType));
         pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
         pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
         if (mAnimation != null) {
@@ -442,56 +444,56 @@
      * No animation is specified.
      * @hide
      */
-    static final int ANIMATION_TYPE_NONE = 0;
+    public static final int ANIMATION_TYPE_NONE = 0;
 
     /**
      * Animation for an app transition.
      * @hide
      */
-    static final int ANIMATION_TYPE_APP_TRANSITION = 1;
+    public static final int ANIMATION_TYPE_APP_TRANSITION = 1;
 
     /**
      * Animation for screen rotation.
      * @hide
      */
-    static final int ANIMATION_TYPE_SCREEN_ROTATION = 1 << 1;
+    public static final int ANIMATION_TYPE_SCREEN_ROTATION = 1 << 1;
 
     /**
      * Animation for dimming.
      * @hide
      */
-    static final int ANIMATION_TYPE_DIMMER = 1 << 2;
+    public static final int ANIMATION_TYPE_DIMMER = 1 << 2;
 
     /**
      * Animation for recent apps.
      * @hide
      */
-    static final int ANIMATION_TYPE_RECENTS = 1 << 3;
+    public static final int ANIMATION_TYPE_RECENTS = 1 << 3;
 
     /**
      * Animation for a {@link WindowState} without animating the activity.
      * @hide
      */
-    static final int ANIMATION_TYPE_WINDOW_ANIMATION = 1 << 4;
+    public static final int ANIMATION_TYPE_WINDOW_ANIMATION = 1 << 4;
 
     /**
      * Animation to control insets. This is actually not an animation, but is used to give the
      * client a leash over the system window causing insets.
      * @hide
      */
-    static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
+    public static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
 
     /**
      * Animation when a fixed rotation transform is applied to a window token.
      * @hide
      */
-    static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
+    public static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
 
     /**
      * Bitmask to include all animation types. This is NOT an {@link AnimationType}
      * @hide
      */
-    static final int ANIMATION_TYPE_ALL = -1;
+    public static final int ANIMATION_TYPE_ALL = -1;
 
     /**
      * The type of the animation.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0529abf..19bf451 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -79,6 +79,7 @@
 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_LOCKTASK;
 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;
@@ -87,9 +88,7 @@
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 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_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -783,7 +782,7 @@
      * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
      */
     ITaskOrganizer mTaskOrganizer;
-    private int mLastTaskOrganizerWindowingMode = -1;
+
     /**
      * Prevent duplicate calls to onTaskAppeared.
      */
@@ -798,6 +797,7 @@
      *     organizer for ordering purposes.</li>
      * </ul>
      */
+    @VisibleForTesting
     boolean mCreatedByOrganizer;
 
     /**
@@ -1650,8 +1650,8 @@
      * Reorder the history stack so that the passed activity is brought to the front.
      */
     final void moveActivityToFrontLocked(ActivityRecord newTop) {
-        if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing and adding activity "
-                + newTop + " to stack at top callers=" + Debug.getCallers(4));
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to stack at top "
+                + "callers=%s", newTop, Debug.getCallers(4));
 
         positionChildAtTop(newTop);
         updateEffectiveIntent();
@@ -1950,8 +1950,8 @@
                         ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
-        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this
-                + " mLockTaskAuth=" + lockTaskAuthToString());
+        ProtoLog.d(WM_DEBUG_LOCKTASK, "setLockTaskAuth: task=%s mLockTaskAuth=%s", this,
+                lockTaskAuthToString());
     }
 
     @Override
@@ -2788,8 +2788,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);
@@ -3820,7 +3828,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
@@ -4820,19 +4831,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();
@@ -4853,29 +4863,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
@@ -6192,10 +6189,6 @@
 
             next.setState(RESUMED, "resumeTopActivityInnerLocked");
 
-            next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
-                    true /* activityChange */, true /* updateOomAdj */,
-                    true /* addPendingTopUid */);
-
             // Have the window manager re-evaluate the orientation of
             // the screen based on the new activity order.
             boolean notUpdated = true;
@@ -6376,7 +6369,8 @@
                 // Here it is!  Now, if this is not yet visible (occluded by another task) to the
                 // user, then just add it without starting; it will get started when the user
                 // navigates back to it.
-                if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task " + task,
+                ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
+                                + "callers: %s", r, task,
                         new RuntimeException("here").fillInStackTrace());
                 rTask.positionChildAtTop(r);
                 ActivityOptions.abort(options);
@@ -6398,8 +6392,8 @@
         task = activityTask;
 
         // Slot the activity into the history stack and proceed
-        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
-                new RuntimeException("here").fillInStackTrace());
+        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to stack to task %s "
+                        + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
         task.positionChildAtTop(r);
 
         // The transition animation and starting window are not needed if {@code allowMoveToFront}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3251110..6550167 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -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..1b779c6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -20,7 +20,10 @@
 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.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
 
@@ -35,13 +38,13 @@
 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;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
@@ -49,6 +52,7 @@
 import java.util.HashMap;
 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 {
@@ -113,6 +126,7 @@
         }
 
         void onTaskAppeared(Task task) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task appeared taskId=%d", task.mTaskId);
             final boolean visible = task.isVisible();
             final RunningTaskInfo taskInfo = task.getTaskInfo();
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
@@ -134,6 +148,7 @@
 
 
         void onTaskVanished(Task task) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task vanished taskId=%d", task.mTaskId);
             final RunningTaskInfo taskInfo = task.getTaskInfo();
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
                 try {
@@ -150,6 +165,7 @@
                 // by the organizer that don't receive that signal
                 return;
             }
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task info changed taskId=%d", task.mTaskId);
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
                 if (!task.isOrganized()) {
                     // This is safe to ignore if the task is no longer organized
@@ -164,6 +180,8 @@
         }
 
         void onBackPressedOnTaskRoot(Task task) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task back pressed on root taskId=%d",
+                    task.mTaskId);
             if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
                 // Skip if the task has not yet received taskAppeared(), except for tasks created
                 // by the organizer that don't receive that signal
@@ -233,9 +251,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 +273,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 +301,30 @@
     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 */);
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
+                        organizer.asBinder(), uid);
+                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);
@@ -347,6 +334,7 @@
     @Override
     public void unregisterTaskOrganizer(ITaskOrganizer organizer) {
         enforceStackPermission("unregisterTaskOrganizer()");
+        final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -354,6 +342,8 @@
                 if (state == null) {
                     return;
                 }
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Unregister task organizer=%s uid=%d",
+                        organizer.asBinder(), uid);
                 state.unlinkDeath();
                 state.dispose();
             }
@@ -362,17 +352,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) {
@@ -398,6 +393,8 @@
                     return null;
                 }
 
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
+                        displayId, windowingMode);
                 final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
                         ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
                         true /* createdByOrganizer */);
@@ -422,6 +419,9 @@
                     throw new IllegalArgumentException(
                             "Attempt to delete task not created by organizer task=" + task);
                 }
+
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete root task display=%d winMode=%d",
+                        task.getDisplayId(), task.getWindowingMode());
                 task.removeImmediately();
                 return true;
             }
@@ -458,6 +458,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);
@@ -627,6 +629,8 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set intercept back pressed on root=%b",
+                        interceptBackPressed);
                 final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 if (state != null) {
                     state.setInterceptBackPressedOnTaskRoot(interceptBackPressed);
@@ -655,18 +659,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 a66cd84..f32781a 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -242,8 +242,8 @@
         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
         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/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index dbbb7ff..e3112ef 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -483,7 +483,7 @@
         final InsetsState insetsState =
                 new InsetsState(insetsPolicy.getInsetsForDispatch(mainWindow));
         mergeInsetsSources(insetsState, mainWindow.getRequestedInsetsState());
-        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrameLw(), insetsState);
+        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
                 mHighResTaskSnapshotScale, insetsState);
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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6e00ab4..ce13867 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -308,7 +308,7 @@
         float defaultWallpaperX = wallpaperWin.isRtl() ? 1f : 0f;
         float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
         float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
-        int availw = wallpaperWin.getFrameLw().right - wallpaperWin.getFrameLw().left - dw;
+        int availw = wallpaperWin.getFrame().right - wallpaperWin.getFrame().left - dw;
         int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
         if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
             offset += mLastWallpaperDisplayOffsetX;
@@ -323,7 +323,7 @@
 
         float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
         float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
-        int availh = wallpaperWin.getFrameLw().bottom - wallpaperWin.getFrameLw().top - dh;
+        int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top - dh;
         offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
         if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
             offset += mLastWallpaperDisplayOffsetY;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7624d4c..c45ccb6 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;
@@ -929,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;
@@ -1847,7 +1848,7 @@
         }
         // We use the visible frame, because we want the animation to morph the window from what
         // was visible to the user to the final destination of the new window.
-        Rect frame = replacedWindow.getVisibleFrameLw();
+        Rect frame = replacedWindow.getVisibleFrame();
         // We treat this as if this activity was opening, so we can trigger the app transition
         // animation and piggy-back on existing transition animation infrastructure.
         final DisplayContent dc = activity.getDisplayContent();
@@ -2068,7 +2069,7 @@
                 outDisplayFrame.setEmpty();
                 return;
             }
-            outDisplayFrame.set(win.getDisplayFrameLw());
+            outDisplayFrame.set(win.getDisplayFrame());
             if (win.inSizeCompatMode()) {
                 outDisplayFrame.scale(win.mInvGlobalScale);
             }
@@ -2388,7 +2389,7 @@
             if (displayPolicy.areSystemBarsForcedShownLw(win)) {
                 result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
             }
-            if (!win.isGoneForLayoutLw()) {
+            if (!win.isGoneForLayout()) {
                 win.mResizedWhileGone = false;
             }
 
@@ -2418,7 +2419,7 @@
             win.getInsetsForRelayout(outContentInsets, outVisibleInsets,
                     outStableInsets);
             outCutout.set(win.getWmDisplayCutout().getDisplayCutout());
-            outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw()));
+            outBackdropFrame.set(win.getBackdropFrame(win.getFrame()));
             outInsetsState.set(win.getInsetsState(), win.isClientLocal());
             if (DEBUG) {
                 Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
@@ -2882,11 +2883,6 @@
     }
 
     @Override
-    public WindowManagerPolicy.WindowState getInputMethodWindowLw() {
-        return mRoot.getCurrentInputMethodWindow();
-    }
-
-    @Override
     public void notifyKeyguardTrustedChanged() {
         mAtmInternal.notifyKeyguardTrustedChanged();
     }
@@ -4740,7 +4736,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;
@@ -4815,11 +4810,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
@@ -4842,24 +4832,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) {
@@ -5504,7 +5476,7 @@
                     // Window has been removed or hidden; no draw will now happen, so stop waiting.
                     ProtoLog.w(WM_DEBUG_SCREEN_ON, "Aborted waiting for drawn: %s", win);
                     container.mWaitingForDrawn.remove(win);
-                } else if (win.hasDrawnLw()) {
+                } else if (win.hasDrawn()) {
                     // Window is now drawn (and shown).
                     ProtoLog.d(WM_DEBUG_SCREEN_ON, "Window drawn win=%s", win);
                     container.mWaitingForDrawn.remove(win);
@@ -5885,6 +5857,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) {
@@ -5896,6 +5873,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) {
@@ -7369,7 +7351,7 @@
             synchronized (mGlobalLock) {
                 WindowState windowState = mWindowMap.get(token);
                 if (windowState != null) {
-                    outBounds.set(windowState.getFrameLw());
+                    outBounds.set(windowState.getFrame());
                 } else {
                     outBounds.setEmpty();
                 }
@@ -8089,8 +8071,7 @@
         h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags;
         h.layoutParamsType = type;
         h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-        h.canReceiveKeys = false;
-        h.hasFocus = false;
+        h.focusable = false;
         h.hasWallpaper = false;
         h.paused = false;
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index d25a648..c7cad2f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -45,6 +46,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -127,6 +129,8 @@
                 if (callback != null) {
                     syncId = startSyncWithOrganizer(callback);
                 }
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d",
+                        syncId);
                 mService.deferWindowLayout();
                 try {
                     ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
@@ -427,6 +431,7 @@
 
     @VisibleForTesting
     void setSyncReady(int id) {
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set sync ready, syncId=%d", id);
         mBLASTSyncEngine.setReady(id);
     }
 
@@ -436,9 +441,10 @@
     }
 
     @Override
-    public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
+    public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) {
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Transaction ready, syncId=%d", syncId);
         final IWindowContainerTransactionCallback callback =
-                mTransactionCallbacksByPendingSyncId.get(mSyncId);
+                mTransactionCallbacksByPendingSyncId.get(syncId);
 
         SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
         for (WindowContainer container : windowContainersReady) {
@@ -446,14 +452,14 @@
         }
 
         try {
-            callback.onTransactionReady(mSyncId, mergedTransaction);
+            callback.onTransactionReady(syncId, mergedTransaction);
         } catch (RemoteException e) {
             // If there's an exception when trying to send the mergedTransaction to the client, we
             // should immediately apply it here so the transactions aren't lost.
             mergedTransaction.apply();
         }
 
-        mTransactionCallbacksByPendingSyncId.remove(mSyncId);
+        mTransactionCallbacksByPendingSyncId.remove(syncId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c714eeb..c5ebace 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -22,9 +22,9 @@
 import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -72,6 +72,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
 import com.android.server.wm.ActivityTaskManagerService.HotPath;
@@ -941,7 +942,7 @@
                 final ActivityRecord r = candidates.remove(0);
                 if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + r
                         + " in state " + r.getState() + " for reason " + reason);
-                r.destroyImmediately(true /*removeFromApp*/, reason);
+                r.destroyImmediately(reason);
                 --maxRelease;
             } while (maxRelease > 0);
         }
@@ -1348,9 +1349,8 @@
             }
             return;
         }
-        if (DEBUG_CONFIGURATION) {
-            Slog.v(TAG_CONFIGURATION, "Sending to proc " + mName + " new config " + config);
-        }
+        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
+                config);
         if (Build.IS_DEBUGGABLE && mHasImeService) {
             // TODO (b/135719017): Temporary log for debugging IME service.
             Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 49e623d..9ff33b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -728,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;
     }
 
@@ -1008,8 +1009,8 @@
         getDisplayContent().reapplyMagnificationSpec();
     }
 
-    @Override
-    public int getOwningUid() {
+    /** Returns the uid of the app that owns this window. */
+    int getOwningUid() {
         return mOwnerUid;
     }
 
@@ -1023,8 +1024,12 @@
         return mOwnerCanAddInternalSystemWindow;
     }
 
-    @Override
-    public boolean canAcquireSleepToken() {
+    /**
+     * Returns {@code true} if the window owner has the permission to acquire a sleep token when
+     * it's visible. That is, they have the permission
+     * {@link androidManifest.permission#DEVICE_POWER}.
+     */
+    boolean canAcquireSleepToken() {
         return mSession.mCanAcquireSleepToken;
     }
 
@@ -1045,7 +1050,7 @@
 
     void computeFrame(DisplayFrames displayFrames) {
         getLayoutingWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
-        computeFrameLw();
+        computeFrame();
         // Update the source frame to provide insets to other windows during layout. If the
         // simulated frames exist, then this is not computing a stable result so just skip.
         if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) {
@@ -1053,8 +1058,10 @@
         }
     }
 
-    @Override
-    public void computeFrameLw() {
+    /**
+     * Perform standard frame computation. The result can be obtained with getFrame() if so desired.
+     */
+    void computeFrame() {
         if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
@@ -1282,32 +1289,41 @@
         }
     }
 
-    @Override
-    public Rect getFrameLw() {
+    /** Retrieves the current frame of the window that the application sees. */
+    Rect getFrame() {
         return mWindowFrames.mFrame;
     }
 
     /** Accessor for testing */
-    Rect getRelativeFrameLw() {
+    Rect getRelativeFrame() {
         return mWindowFrames.mRelFrame;
     }
 
-    @Override
-    public Rect getDisplayFrameLw() {
+    /** Retrieves the frame of the display that this window was last laid out in. */
+    Rect getDisplayFrame() {
         return mWindowFrames.mDisplayFrame;
     }
 
-    @Override
-    public Rect getContentFrameLw() {
+    /**
+     * Retrieves the frame of the content area that this window was last laid out in. This is the
+     * area in which the content of the window should be placed. It will be smaller than the display
+     * frame to account for screen decorations such as a status bar or soft keyboard.
+     */
+    Rect getContentFrame() {
         return mWindowFrames.mContentFrame;
     }
 
-    @Override
-    public Rect getVisibleFrameLw() {
+    /**
+     * Retrieves the frame of the visible area that this window was last laid out in. This is the
+     * area of the screen in which the window will actually be fully visible. It will be smaller
+     * than the content frame to account for transient UI elements blocking it such as an input
+     * method's candidates UI.
+     */
+    Rect getVisibleFrame() {
         return mWindowFrames.mVisibleFrame;
     }
 
-    Rect getStableFrameLw() {
+    Rect getStableFrame() {
         return mWindowFrames.mStableFrame;
     }
 
@@ -1336,32 +1352,17 @@
     }
 
     @Override
-    public boolean getGivenInsetsPendingLw() {
-        return mGivenInsetsPending;
-    }
-
-    @Override
-    public Rect getGivenContentInsetsLw() {
-        return mGivenContentInsets;
-    }
-
-    @Override
-    public Rect getGivenVisibleInsetsLw() {
-        return mGivenVisibleInsets;
-    }
-
-    @Override
     public WindowManager.LayoutParams getAttrs() {
         return mAttrs;
     }
 
-    @Override
-    public int getSystemUiVisibility() {
+    /** Retrieves the current system UI visibility flags associated with this window. */
+    int getSystemUiVisibility() {
         return mSystemUiVisibility;
     }
 
-    @Override
-    public int getSurfaceLayer() {
+    /** Gets the layer at which this window's surface will be Z-ordered. */
+    int getSurfaceLayer() {
         return mLayer;
     }
 
@@ -1375,8 +1376,8 @@
         return mActivityRecord != null ? mActivityRecord.appToken : null;
     }
 
-    @Override
-    public boolean isVoiceInteraction() {
+    /** Returns true if this window is participating in voice interaction. */
+    boolean isVoiceInteraction() {
         return mActivityRecord != null && mActivityRecord.mVoiceInteraction;
     }
 
@@ -1390,7 +1391,7 @@
      */
     void updateResizingWindowIfNeeded() {
         final WindowStateAnimator winAnimator = mWinAnimator;
-        if (!mHasSurface || getDisplayContent().mLayoutSeq != mLayoutSeq || isGoneForLayoutLw()) {
+        if (!mHasSurface || getDisplayContent().mLayoutSeq != mLayoutSeq || isGoneForLayout()) {
             return;
         }
 
@@ -1463,7 +1464,7 @@
                 mWmService.mResizingWindows.add(this);
             }
         } else if (getOrientationChanging()) {
-            if (isDrawnLw()) {
+            if (isDrawn()) {
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "Orientation not waiting for draw in %s, surfaceController %s", this,
                         winAnimator.mSurfaceController);
@@ -1638,9 +1639,16 @@
                 : DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
     }
 
-    @Override
-    public boolean hasAppShownWindows() {
-        return mActivityRecord != null && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+    /**
+     * Returns true if, at any point, the application token associated with this window has actually
+     * displayed any windows. This is most useful with the "starting up" window to determine if any
+     * windows were displayed when it is closed.
+     *
+     * @return {@code true} if one or more windows have been displayed, else false.
+     */
+    boolean hasAppShownWindows() {
+        return mActivityRecord != null
+                && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
     }
 
     boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
@@ -1662,7 +1670,7 @@
 
     @Override
     boolean hasContentToDisplay() {
-        if (!mAppFreezing && isDrawnLw() && (mViewVisibility == View.VISIBLE
+        if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
                 || (isAnimating(TRANSITION | PARENTS)
                 && !getDisplayContent().mAppTransition.isTransitionSet()))) {
             return true;
@@ -1850,10 +1858,9 @@
      * Like isOnScreen, but returns false if the surface hasn't yet
      * been drawn.
      */
-    @Override
-    public boolean isDisplayedLw() {
+    boolean isDisplayed() {
         final ActivityRecord atoken = mActivityRecord;
-        return isDrawnLw() && isVisibleByPolicy()
+        return isDrawn() && isVisibleByPolicy()
                 && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
                         || isAnimating(TRANSITION | PARENTS));
     }
@@ -1866,8 +1873,8 @@
         return isAnimating(TRANSITION | PARENTS);
     }
 
-    @Override
-    public boolean isGoneForLayoutLw() {
+    /** Returns {@code true} if this window considered to be gone for purposes of layout. */
+    boolean isGoneForLayout() {
         final ActivityRecord atoken = mActivityRecord;
         return mViewVisibility == View.GONE
                 || !mRelayoutCalled
@@ -1894,11 +1901,11 @@
     }
 
     /**
-     * Returns true if the window has a surface that it has drawn a
-     * complete UI in to.
+     * Returns true if the window has a surface that it has drawn a complete UI in to. Note that
+     * this is different from {@link #hasDrawn()} in that it also returns true if the window is
+     * READY_TO_SHOW, but was not yet promoted to HAS_DRAWN.
      */
-    @Override
-    public boolean isDrawnLw() {
+    boolean isDrawn() {
         return mHasSurface && !mDestroying &&
                 (mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
     }
@@ -1913,7 +1920,7 @@
         // to determine if it's occluding apps.
         return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
                 || (mIsWallpaper && mWallpaperVisible))
-                && isDrawnLw() && !isAnimating(TRANSITION | PARENTS);
+                && isDrawn() && !isAnimating(TRANSITION | PARENTS);
     }
 
     /** @see WindowManagerInternal#waitForAllWindowsDrawn */
@@ -1928,7 +1935,7 @@
                 return;
             }
             if (mAttrs.type == TYPE_APPLICATION_STARTING) {
-                if (isDrawnLw()) {
+                if (isDrawn()) {
                     // Unnecessary to redraw a drawn starting window.
                     return;
                 }
@@ -2015,11 +2022,11 @@
     @Override
     void onResize() {
         final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;
-        if (mHasSurface && !isGoneForLayoutLw() && !resizingWindows.contains(this)) {
+        if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
             ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
             resizingWindows.add(this);
         }
-        if (isGoneForLayoutLw()) {
+        if (isGoneForLayout()) {
             mResizedWhileGone = true;
         }
 
@@ -2057,10 +2064,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);
@@ -2506,7 +2520,7 @@
 
     /** Returns true if the replacement window was removed. */
     boolean removeReplacedWindowIfNeeded(WindowState replacement) {
-        if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawnLw()) {
+        if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) {
             replacement.mSkipEnterAnimationForSeamlessReplacement = false;
             removeReplacedWindow();
             return true;
@@ -2740,7 +2754,7 @@
             mLayoutNeeded = true;
         }
 
-        if (isDrawnLw() && mToken.okToAnimate()) {
+        if (isDrawn() && mToken.okToAnimate()) {
             mWinAnimator.applyEnterAnimationLocked();
         }
     }
@@ -2865,8 +2879,8 @@
         return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
     }
 
-    @Override
-    public boolean canReceiveKeys() {
+    /** Returns {@code true} if this window desires key events. */
+    boolean canReceiveKeys() {
         return canReceiveKeys(false /* fromUserTouch */);
     }
 
@@ -2915,8 +2929,13 @@
                 && recentsAnimationController.shouldApplyInputConsumer(mActivityRecord);
     }
 
-    @Override
-    public boolean hasDrawnLw() {
+    /**
+     * Returns {@code true} if this window has been shown on screen at some time in the past.
+     *
+     * @deprecated Use {@link #isDrawnLw} or any of the other drawn/visibility methods.
+     */
+    @Deprecated
+    boolean hasDrawn() {
         return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
     }
 
@@ -3150,8 +3169,8 @@
         }
     }
 
-    @Override
-    public boolean isAlive() {
+    /** Checks whether the process hosting this window is currently alive. */
+    boolean isAlive() {
         return mClient.asBinder().isBinderAlive();
     }
 
@@ -3331,16 +3350,6 @@
         mLastExclusionLogUptimeMillis[EXCLUSION_RIGHT] = now;
     }
 
-    @Override
-    public boolean isDefaultDisplay() {
-        final DisplayContent displayContent = getDisplayContent();
-        if (displayContent == null) {
-            // Only a window that was on a non-default display can be detached from it.
-            return false;
-        }
-        return displayContent.isDefaultDisplay;
-    }
-
     /** @return {@code true} if this window can be shown to all users. */
     boolean showForAllUsers() {
 
@@ -3400,10 +3409,10 @@
             // All window frames that are fullscreen extend above status bar, but some don't extend
             // below navigation bar. Thus, check for display frame for top/left and stable frame for
             // bottom right.
-            if (win.getFrameLw().left <= win.getDisplayFrameLw().left
-                    && win.getFrameLw().top <= win.getDisplayFrameLw().top
-                    && win.getFrameLw().right >= win.getStableFrameLw().right
-                    && win.getFrameLw().bottom >= win.getStableFrameLw().bottom) {
+            if (win.getFrame().left <= win.getDisplayFrame().left
+                    && win.getFrame().top <= win.getDisplayFrame().top
+                    && win.getFrame().right >= win.getStableFrame().right
+                    && win.getFrame().bottom >= win.getStableFrame().bottom) {
                 // Is a fullscreen window, like the clock alarm. Show to everyone.
                 return true;
             }
@@ -3561,6 +3570,13 @@
     }
 
     void reportResized() {
+        // If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now
+        // since it will be destroyed anyway. This also prevents the client from receiving
+        // windowing mode change before it is destroyed.
+        if (mActivityRecord != null && mActivityRecord.isRelaunching()) {
+            return;
+        }
+
         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wm.reportResized_" + getWindowTag());
         }
@@ -3766,11 +3782,11 @@
      *          is transitioning into/out-of fullscreen. */
     boolean isLetterboxedAppWindow() {
         return !inMultiWindowMode() && !matchesDisplayBounds()
-                || isLetterboxedForDisplayCutoutLw();
+                || isLetterboxedForDisplayCutout();
     }
 
-    @Override
-    public boolean isLetterboxedForDisplayCutoutLw() {
+    /** Returns {@code true} if the window is letterboxed for the display cutout. */
+    boolean isLetterboxedForDisplayCutout() {
         if (mActivityRecord == null) {
             // Only windows with an ActivityRecord are letterboxed.
             return false;
@@ -3874,7 +3890,7 @@
         // background.
         return (getDisplayContent().mDividerControllerLocked.isResizing()
                         || mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) &&
-                !task.inFreeformWindowingMode() && !isGoneForLayoutLw();
+                !task.inFreeformWindowingMode() && !isGoneForLayout();
 
     }
 
@@ -4290,7 +4306,7 @@
 
     private boolean isParentWindowGoneForLayout() {
         final WindowState parent = getParentWindow();
-        return parent != null && parent.isGoneForLayoutLw();
+        return parent != null && parent.isGoneForLayout();
     }
 
     void setWillReplaceWindow(boolean animate) {
@@ -4403,8 +4419,7 @@
         return null;
     }
 
-    @Override
-    public int getRotationAnimationHint() {
+    int getRotationAnimationHint() {
         if (mActivityRecord != null) {
             return mActivityRecord.mRotationAnimationHint;
         } else {
@@ -4860,7 +4875,7 @@
     }
 
     boolean hasVisibleNotDrawnWallpaper() {
-        if (mWallpaperVisible && !isDrawnLw()) {
+        if (mWallpaperVisible && !isDrawn()) {
             return true;
         }
         for (int j = mChildren.size() - 1; j >= 0; --j) {
@@ -4884,9 +4899,9 @@
             return;
         }
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawnLw()
+            Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
                     + ", animating=" + isAnimating(TRANSITION | PARENTS));
-            if (!isDrawnLw()) {
+            if (!isDrawn()) {
                 Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
                         + " pv=" + isVisibleByPolicy()
                         + " mDrawState=" + mWinAnimator.mDrawState
@@ -4897,7 +4912,7 @@
         }
 
         results.numInteresting++;
-        if (isDrawnLw()) {
+        if (isDrawn()) {
             results.numDrawn++;
             if (!isAnimating(TRANSITION | PARENTS)) {
                 results.numVisible++;
@@ -5033,7 +5048,7 @@
     int relayoutVisibleWindow(int result, int attrChanges) {
         final boolean wasVisible = isVisibleLw();
 
-        result |= (!wasVisible || !isDrawnLw()) ? RELAYOUT_RES_FIRST_TIME : 0;
+        result |= (!wasVisible || !isDrawn()) ? RELAYOUT_RES_FIRST_TIME : 0;
 
         if (mAnimatingExit) {
             Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit="
@@ -5605,15 +5620,14 @@
         return !mTapExcludeRegion.isEmpty();
     }
 
-    @Override
-    public boolean isInputMethodTarget() {
+    boolean isInputMethodTarget() {
         return getDisplayContent().mInputMethodTarget == this;
     }
 
     long getFrameNumber() {
         // Return the frame number in which changes requested in this layout will be rendered or
         // -1 if we do not expect the frame to be rendered.
-        return getFrameLw().isEmpty() ? -1 : mFrameNumber;
+        return getFrame().isEmpty() ? -1 : mFrameNumber;
     }
 
     void setFrameNumber(long frameNumber) {
@@ -5676,8 +5690,8 @@
         return mWindowFrames.mVisibleInsets;
     }
 
-    @Override
-    public WindowFrames getWindowFrames() {
+    /** Returns the {@link WindowFrames} associated with this {@link WindowState}. */
+    WindowFrames getWindowFrames() {
         return mWindowFrames;
     }
 
@@ -5785,7 +5799,7 @@
         // won't exactly match the final freeform window frame (e.g. when overlapping with
         // the status bar). In that case we need to use the final frame.
         if (inFreeformWindowingMode()) {
-            outFrame.set(getFrameLw());
+            outFrame.set(getFrame());
         } else if (isLetterboxedAppWindow() || mToken.isFixedRotationTransforming()) {
             // 1. The letterbox surfaces should be animated with the owner activity, so use task
             //    bounds to include them.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 92177ab..1bd712c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -38,6 +38,7 @@
 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.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;
@@ -525,13 +526,13 @@
 
         if (DEBUG) {
             Slog.v(TAG, "Got surface: " + mSurfaceController
-                    + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top);
+                    + ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
         }
 
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
             WindowManagerService.logSurface(w, "CREATE pos=("
-                    + w.getFrameLw().left + "," + w.getFrameLw().top + ") ("
+                    + w.getFrame().left + "," + w.getFrame().top + ") ("
                     + width + "x" + height + ")" + " HIDE", false);
         }
 
@@ -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)
@@ -968,7 +896,7 @@
 
             // There is no need to wait for an animation change if our window is gone for layout
             // already as we'll never be visible.
-            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+            if (w.getOrientationChanging() && w.isGoneForLayout()) {
                 ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change skips hidden %s", w);
                 w.setOrientationChanging(false);
             }
@@ -992,7 +920,7 @@
             // really hidden (gone for layout), there is no point in still waiting for it.
             // Note that this does introduce a potential glitch if the window becomes unhidden
             // before it has drawn for the new orientation.
-            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+            if (w.getOrientationChanging() && w.isGoneForLayout()) {
                 w.setOrientationChanging(false);
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "Orientation change skips hidden %s", w);
@@ -1070,7 +998,7 @@
         }
 
         if (w.getOrientationChanging()) {
-            if (!w.isDrawnLw()) {
+            if (!w.isDrawn()) {
                 mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
                 mAnimator.mLastWindowFreezeSource = w;
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -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/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index b3f3a5e..9aca848 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -40,14 +40,12 @@
 namespace android {
 
 static JavaVM* sJvm = nullptr;
-
 static jmethodID sMethodIdOnComplete;
-
 static struct {
     jfieldID id;
     jfieldID scale;
     jfieldID delay;
-} gPrimitiveClassInfo;
+} sPrimitiveClassInfo;
 
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
                 static_cast<uint8_t>(aidl::EffectStrength::LIGHT));
@@ -77,100 +75,117 @@
 static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
                 static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
-static inline void callVibrationOnComplete(jobject vibration) {
-    if (vibration == nullptr) {
-        return;
+class NativeVibratorService {
+public:
+    NativeVibratorService(JNIEnv* env, jobject callbackListener)
+          : mController(std::make_unique<vibrator::HalController>()),
+            mCallbackListener(env->NewGlobalRef(callbackListener)) {
+        LOG_ALWAYS_FATAL_IF(mCallbackListener == nullptr,
+                            "Unable to create global reference to vibration callback handler");
     }
-    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
-    jniEnv->CallVoidMethod(vibration, sMethodIdOnComplete);
-    jniEnv->DeleteGlobalRef(vibration);
-}
+
+    ~NativeVibratorService() {
+        auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+        jniEnv->DeleteGlobalRef(mCallbackListener);
+    }
+
+    vibrator::HalController* controller() const { return mController.get(); }
+
+    std::function<void()> createCallback(jlong vibrationId) {
+        return [vibrationId, this]() {
+            auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId);
+        };
+    }
+
+private:
+    const std::unique_ptr<vibrator::HalController> mController;
+    const jobject mCallbackListener;
+};
 
 static aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) {
     aidl::CompositeEffect effect;
     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<int32_t>(env->GetIntField(primitive, gPrimitiveClassInfo.delay));
+            env->GetIntField(primitive, sPrimitiveClassInfo.id));
+    effect.scale = static_cast<float>(env->GetFloatField(primitive, sPrimitiveClassInfo.scale));
+    effect.delayMs = static_cast<int32_t>(env->GetIntField(primitive, sPrimitiveClassInfo.delay));
     return effect;
 }
 
-static void destroyVibratorController(void* rawVibratorController) {
-    vibrator::HalController* vibratorController =
-            reinterpret_cast<vibrator::HalController*>(rawVibratorController);
-    if (vibratorController) {
-        delete vibratorController;
+static void destroyNativeService(void* servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service) {
+        delete service;
     }
 }
 
-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 vibratorInit(JNIEnv* env, jclass /* clazz */, jobject callbackListener) {
+    std::unique_ptr<NativeVibratorService> service =
+            std::make_unique<NativeVibratorService>(env, callbackListener);
+    service->controller()->init();
+    return reinterpret_cast<jlong>(service.release());
 }
 
 static jlong vibratorGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyVibratorController));
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeService));
 }
 
-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");
+static jboolean vibratorExists(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorExists failed because native service was not initialized");
         return JNI_FALSE;
     }
-    return controller->ping().isOk() ? JNI_TRUE : JNI_FALSE;
+    return service->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");
+static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong timeoutMs,
+                       jlong vibrationId) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorOn failed because native service was not initialized");
         return;
     }
-    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
-    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
-    controller->on(std::chrono::milliseconds(timeoutMs), callback);
+    auto callback = service->createCallback(vibrationId);
+    service->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");
+static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorOff failed because native service was not initialized");
         return;
     }
-    controller->off();
+    service->controller()->off();
 }
 
-static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
                                  jint amplitude) {
-    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
-    if (controller == nullptr) {
-        ALOGE("vibratorSetAmplitude failed because controller was not initialized");
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorSetAmplitude failed because native service was not initialized");
         return;
     }
-    controller->setAmplitude(static_cast<int32_t>(amplitude));
+    service->controller()->setAmplitude(static_cast<int32_t>(amplitude));
 }
 
-static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
                                        jboolean enabled) {
-    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
-    if (controller == nullptr) {
-        ALOGE("vibratorSetExternalControl failed because controller was not initialized");
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorSetExternalControl failed because native service was not initialized");
         return;
     }
-    controller->setExternalControl(enabled);
+    service->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");
+static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorGetSupportedEffects failed because native service was not initialized");
         return nullptr;
     }
-    auto result = controller->getSupportedEffects();
+    auto result = service->controller()->getSupportedEffects();
     if (!result.isOk()) {
         return nullptr;
     }
@@ -181,14 +196,13 @@
     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");
+static jintArray vibratorGetSupportedPrimitives(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorGetSupportedPrimitives failed because native service was not initialized");
         return nullptr;
     }
-    auto result = controller->getSupportedPrimitives();
+    auto result = service->controller()->getSupportedPrimitives();
     if (!result.isOk()) {
         return nullptr;
     }
@@ -199,26 +213,25 @@
     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");
+static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong effect,
+                                   jlong strength, jlong vibrationId) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorPerformEffect failed because native service 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);
+    auto callback = service->createCallback(vibrationId);
+    auto result = service->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");
+static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+                                          jobjectArray composition, jlong vibrationId) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorPerformComposedEffect failed because native service was not initialized");
         return;
     }
     size_t size = env->GetArrayLength(composition);
@@ -227,54 +240,52 @@
         jobject element = env->GetObjectArrayElement(composition, i);
         effects.push_back(effectFromJavaPrimitive(env, element));
     }
-    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
-    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
-    controller->performComposedEffect(effects, callback);
+    auto callback = service->createCallback(vibrationId);
+    service->controller()->performComposedEffect(effects, callback);
 }
 
-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");
+static jlong vibratorGetCapabilities(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorGetCapabilities failed because native service was not initialized");
         return 0;
     }
-    auto result = controller->getCapabilities();
+    auto result = service->controller()->getCapabilities();
     return result.isOk() ? static_cast<jlong>(result.value()) : 0;
 }
 
-static void vibratorAlwaysOnEnable(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong id,
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass /* clazz */, jlong servicePtr, 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");
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorAlwaysOnEnable failed because native service was not initialized");
         return;
     }
-    controller->alwaysOnEnable(static_cast<int32_t>(id), static_cast<aidl::Effect>(effect),
-                               static_cast<aidl::EffectStrength>(strength));
+    service->controller()->alwaysOnEnable(static_cast<int32_t>(id),
+                                          static_cast<aidl::Effect>(effect),
+                                          static_cast<aidl::EffectStrength>(strength));
 }
 
-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");
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong id) {
+    NativeVibratorService* service = reinterpret_cast<NativeVibratorService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("vibratorAlwaysOnDisable failed because native service was not initialized");
         return;
     }
-    controller->alwaysOnDisable(static_cast<int32_t>(id));
+    service->controller()->alwaysOnDisable(static_cast<int32_t>(id));
 }
 
 static const JNINativeMethod method_table[] = {
-        {"vibratorInit", "()J", (void*)vibratorInit},
+        {"vibratorInit", "(Lcom/android/server/VibratorService$OnCompleteListener;)J",
+         (void*)vibratorInit},
         {"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
         {"vibratorExists", "(J)Z", (void*)vibratorExists},
-        {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
+        {"vibratorOn", "(JJJ)V", (void*)vibratorOn},
         {"vibratorOff", "(J)V", (void*)vibratorOff},
         {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
-        {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J",
-         (void*)vibratorPerformEffect},
+        {"vibratorPerformEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
         {"vibratorPerformComposedEffect",
-         "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
-         "VibratorService$Vibration;)V",
+         "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)V",
          (void*)vibratorPerformComposedEffect},
         {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
         {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
@@ -284,18 +295,17 @@
         {"vibratorAlwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
 };
 
-int register_android_server_VibratorService(JavaVM* vm, JNIEnv* env) {
-    sJvm = vm;
-    sMethodIdOnComplete =
-            GetMethodIDOrDie(env,
-                             FindClassOrDie(env, "com/android/server/VibratorService$Vibration"),
-                             "onComplete", "()V");
+int register_android_server_VibratorService(JavaVM* jvm, JNIEnv* env) {
+    sJvm = jvm;
+    jclass listenerClass =
+            FindClassOrDie(env, "com/android/server/VibratorService$OnCompleteListener");
+    sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)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");
+    sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
+    sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
+    sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
 
     return jniRegisterNativeMethods(env, "com/android/server/VibratorService", method_table,
                                     NELEM(method_table));
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 0277f16..46e6f91 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -33,6 +33,8 @@
 
 #include <android-base/unique_fd.h>
 
+#include <type_traits>
+
 namespace android {
 
 namespace {
@@ -53,7 +55,7 @@
 
     fsverity_enable_arg arg = {};
     arg.version = 1;
-    arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+    arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; // hardcoded in measureFsverity below
     arg.block_size = 4096;
     arg.salt_size = 0;
     arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
@@ -85,9 +87,41 @@
     return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
 }
 
+int measureFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArray digest) {
+    static constexpr auto kDigestSha256 = 32;
+    using Storage = std::aligned_storage_t<sizeof(fsverity_digest) + kDigestSha256>;
+
+    Storage bytes;
+    fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
+    data->digest_size = kDigestSha256; // the only input/output parameter
+
+    ScopedUtfChars path(env, filePath);
+    ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
+    if (rfd.get() < 0) {
+        return rfd.get();
+    }
+    if (auto err = ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data); err < 0) {
+        return err;
+    }
+
+    if (data->digest_algorithm != FS_VERITY_HASH_ALG_SHA256) {
+        return -EINVAL;
+    }
+
+    if (digest != nullptr && data->digest_size > 0) {
+        auto digestSize = env->GetArrayLength(digest);
+        if (data->digest_size > digestSize) {
+            return -E2BIG;
+        }
+        env->SetByteArrayRegion(digest, 0, data->digest_size, (const jbyte *)data->digest);
+    }
+
+    return 0;
+}
 const JNINativeMethod sMethods[] = {
         {"enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity},
         {"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
+        {"measureFsverityNative", "(Ljava/lang/String;[B)I", (void *)measureFsverity},
 };
 
 }  // namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6154bef..c359b19 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -302,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;
@@ -322,7 +321,6 @@
 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;
 
@@ -575,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;
 
@@ -633,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;
 
@@ -748,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)) {
@@ -1435,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
@@ -1566,7 +1515,15 @@
     /**
      * Creates a new {@link CallerIdentity} object to represent the caller's identity.
      */
-    private CallerIdentity getCallerIdentity(String callerPackage) {
+    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)) {
@@ -2127,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.
      *
@@ -2145,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;
         }
 
@@ -2844,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,
@@ -2986,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;
         }
@@ -2997,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);
@@ -3009,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) {
@@ -3025,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();
@@ -3046,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();
@@ -3156,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) {
@@ -3309,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;
 
@@ -3521,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;
 
@@ -3547,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<>();
             }
@@ -3560,7 +3624,7 @@
             if (!providers.contains(packageName)) {
                 providers.add(packageName);
                 changedProviders = new ArrayList<>(providers);
-                saveSettingsLocked(userId);
+                saveSettingsLocked(identity.getUserId());
             }
         }
 
@@ -3570,7 +3634,8 @@
                 .write();
 
         if (changedProviders != null) {
-            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
+            mLocalService.notifyCrossProfileProvidersChanged(identity.getUserId(),
+                    changedProviders);
             return true;
         }
 
@@ -3579,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;
@@ -3592,7 +3657,7 @@
             List<String> providers = activeAdmin.crossProfileWidgetProviders;
             if (providers.remove(packageName)) {
                 changedProviders = new ArrayList<>(providers);
-                saveSettingsLocked(userId);
+                saveSettingsLocked(identity.getUserId());
             }
         }
 
@@ -3602,7 +3667,8 @@
                 .write();
 
         if (changedProviders != null) {
-            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
+            mLocalService.notifyCrossProfileProvidersChanged(identity.getUserId(),
+                    changedProviders);
             return true;
         }
 
@@ -3611,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;
@@ -3656,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);
         }
@@ -3862,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);
@@ -3902,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 =
@@ -3919,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()) {
@@ -3951,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()) {
@@ -3970,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);
 
@@ -4058,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);
                 }
@@ -4106,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)
@@ -4120,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);
@@ -4191,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);
         }
 
@@ -4392,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);
@@ -4465,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);
@@ -4504,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;
         }
 
@@ -4597,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);
@@ -5677,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()) {
@@ -5854,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");
                 }
@@ -5893,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);
@@ -5972,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;
@@ -6051,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);
@@ -6079,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);
@@ -6090,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);
@@ -6101,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);
@@ -6112,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);
@@ -6176,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
@@ -6310,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) {
@@ -6340,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.
@@ -6485,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) {
@@ -6679,45 +6816,24 @@
         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);
         }
     }
 
@@ -6761,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());
         }
     }
 
@@ -7033,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()) {
@@ -7229,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);
@@ -7783,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);
     }
@@ -8140,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) {
@@ -8249,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) {
@@ -8315,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);
@@ -8569,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();
@@ -8769,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 {
@@ -8801,8 +8816,7 @@
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.permittedAccessiblityServices = packageList;
             saveSettingsLocked(UserHandle.getCallingUserId());
         }
@@ -8822,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;
         }
     }
@@ -8920,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;
@@ -8939,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;
@@ -8960,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;
         }
     }
@@ -9037,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;
     }
@@ -9058,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;
         }
     }
@@ -9420,8 +9438,7 @@
         Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true
-                    /*excludeDying*/);
+            final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
             final List<UserHandle> userHandles = new ArrayList<>();
             for (UserInfo userInfo : userInfos) {
                 UserHandle userHandle = userInfo.getUserHandle();
@@ -9922,10 +9939,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<>();
 
@@ -9992,10 +10013,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);
@@ -10015,12 +10038,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
@@ -10036,16 +10061,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;
@@ -10058,12 +10089,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
@@ -10079,16 +10112,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;
@@ -10159,12 +10198,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
@@ -10180,9 +10221,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;
         }
     }
@@ -10318,7 +10361,7 @@
 
     private void maybeClearLockTaskPolicyLocked() {
         mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             for (int i = userInfos.size() - 1; i >= 0; i--) {
                 int userId = userInfos.get(i).id;
                 if (canUserUseLockTaskLocked(userId)) {
@@ -10805,7 +10848,7 @@
      * them.
      */
     void updateUserSetupCompleteAndPaired() {
-        List<UserInfo> users = mUserManager.getUsers(true);
+        List<UserInfo> users = mUserManager.getAliveUsers();
         final int N = users.size();
         for (int i = 0; i < N; i++) {
             int userHandle = users.get(i).id;
@@ -12008,14 +12051,6 @@
     }
 
     @Override
-    public boolean isSystemOnlyUser(ComponentName admin) {
-        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, "ComponentName is null");
         final CallerIdentity identity = getCallerIdentity(admin);
@@ -12138,13 +12173,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)
@@ -12157,7 +12191,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()) {
@@ -12173,10 +12211,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;
         }
     }
@@ -12186,7 +12224,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);
@@ -12202,15 +12244,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());
             }
         }
     }
@@ -12221,10 +12262,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;
         }
     }
@@ -12246,7 +12287,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);
@@ -12260,20 +12305,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;
             });
         }
@@ -12310,9 +12356,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;
         }
@@ -12337,12 +12386,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
@@ -12355,7 +12398,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.");
         }
@@ -12527,7 +12570,7 @@
 
     private boolean areAllUsersAffiliatedWithDeviceLocked() {
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            final List<UserInfo> userInfos = mUserManager.getAliveUsers();
             for (int i = 0; i < userInfos.size(); i++) {
                 int userId = userInfos.get(i).id;
                 if (!isUserAffiliatedWithDeviceLocked(userId)) {
@@ -12996,7 +13039,7 @@
                     }
                 } else {
                     // Caller is the device owner: Look for profile owners that it can bind to.
-                    final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+                    final List<UserInfo> userInfos = mUserManager.getAliveUsers();
                     for (int i = 0; i < userInfos.size(); i++) {
                         final int userId = userInfos.get(i).id;
                         if (userId != callingUserId && canUserBindToDeviceOwnerLocked(userId)) {
@@ -13483,7 +13526,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));
@@ -14100,12 +14144,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)
@@ -14121,10 +14165,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;
         }
     }
@@ -14136,8 +14180,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) {
@@ -14159,7 +14206,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) {
@@ -14176,16 +14227,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);
@@ -14208,10 +14260,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;
         }
     }
@@ -14221,7 +14273,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();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3cdd482..7649af4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -169,7 +169,7 @@
             // First, try to read from the legacy file.
             final File legacy = getLegacyConfigFile();
 
-            final List<UserInfo> users = mUserManager.getUsers(true);
+            final List<UserInfo> users = mUserManager.getAliveUsers();
 
             if (readLegacyOwnerFileLocked(legacy)) {
                 if (DEBUG) {
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/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 0ae10b6..41945a2 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -237,11 +237,9 @@
     return ok();
 }
 
-binder::Status BinderIncrementalService::isFileRangeLoaded(int32_t storageId,
-                                                           const std::string& path, int64_t start,
-                                                           int64_t end, bool* _aidl_return) {
-    // TODO: implement
-    *_aidl_return = false;
+binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId,
+                                                            float* _aidl_return) {
+    *_aidl_return = mImpl.getLoadingProgress(storageId);
     return ok();
 }
 
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 1015494..8b40350 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -66,8 +66,7 @@
                             int32_t destStorageId, const std::string& destPath,
                             int32_t* _aidl_return) final;
     binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final;
-    binder::Status isFileRangeLoaded(int32_t storageId, const std::string& path, int64_t start,
-                                     int64_t end, bool* _aidl_return) final;
+    binder::Status getLoadingProgress(int32_t storageId, float* _aidl_return) final;
     binder::Status getMetadataByPath(int32_t storageId, const std::string& path,
                                      std::vector<uint8_t>* _aidl_return) final;
     binder::Status getMetadataById(int32_t storageId, const std::vector<uint8_t>& id,
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index f7082a9..9836262e 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -37,7 +37,6 @@
 #include "Metadata.pb.h"
 
 using namespace std::literals;
-namespace fs = std::filesystem;
 
 constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
 constexpr const char* kOpUsage = "android:loader_usage_stats";
@@ -276,6 +275,7 @@
         mJni(sm.getJni()),
         mLooper(sm.getLooper()),
         mTimedQueue(sm.getTimedQueue()),
+        mFs(sm.getFs()),
         mIncrementalDir(rootDir) {
     CHECK(mVold) << "Vold service is unavailable";
     CHECK(mDataLoaderManager) << "DataLoaderManagerService is unavailable";
@@ -283,6 +283,7 @@
     CHECK(mJni) << "JNI is unavailable";
     CHECK(mLooper) << "Looper is unavailable";
     CHECK(mTimedQueue) << "TimedQueue is unavailable";
+    CHECK(mFs) << "Fs is unavailable";
 
     mJobQueue.reserve(16);
     mJobProcessor = std::thread([this]() {
@@ -344,7 +345,8 @@
             }
             dprintf(fd, "    storages (%d): {\n", int(mnt.storages.size()));
             for (auto&& [storageId, storage] : mnt.storages) {
-                dprintf(fd, "      [%d] -> [%s]\n", storageId, storage.name.c_str());
+                dprintf(fd, "      [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(),
+                        (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()) * 100));
             }
             dprintf(fd, "    }\n");
 
@@ -1671,6 +1673,45 @@
     return mRunning;
 }
 
+float IncrementalService::getLoadingProgress(StorageId storage) const {
+    std::unique_lock l(mLock);
+    const auto ifs = getIfsLocked(storage);
+    if (!ifs) {
+        LOG(ERROR) << "getLoadingProgress failed, invalid storageId: " << storage;
+        return -EINVAL;
+    }
+    const auto storageInfo = ifs->storages.find(storage);
+    if (storageInfo == ifs->storages.end()) {
+        LOG(ERROR) << "getLoadingProgress failed, no storage: " << storage;
+        return -EINVAL;
+    }
+    l.unlock();
+    return getLoadingProgressFromPath(*ifs, storageInfo->second.name);
+}
+
+float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs,
+                                                     std::string_view storagePath) const {
+    size_t totalBlocks = 0, filledBlocks = 0;
+    const auto filePaths = mFs->listFilesRecursive(storagePath);
+    for (const auto& filePath : filePaths) {
+        const auto [filledBlocksCount, totalBlocksCount] =
+                mIncFs->countFilledBlocks(ifs.control, filePath);
+        if (filledBlocksCount < 0) {
+            LOG(ERROR) << "getLoadingProgress failed to get filled blocks count for: " << filePath
+                       << " errno: " << filledBlocksCount;
+            return filledBlocksCount;
+        }
+        totalBlocks += totalBlocksCount;
+        filledBlocks += filledBlocksCount;
+    }
+
+    if (totalBlocks == 0) {
+        // No file in the storage or files are empty; regarded as fully loaded
+        return 1;
+    }
+    return (float)filledBlocks / (float)totalBlocks;
+}
+
 bool IncrementalService::perfLoggingEnabled() {
     static const bool enabled = base::GetBoolProperty("incremental.perflogging", false);
     return enabled;
@@ -2029,11 +2070,13 @@
 
         // Healthcheck depends on timestamp of the oldest pending read.
         // To get it, we need to re-open a pendingReads FD to get a full list of reads.
-        // Additionally we need to re-register for epoll with fresh FDs in case there are no reads.
+        // Additionally we need to re-register for epoll with fresh FDs in case there are no
+        // reads.
         const auto now = Clock::now();
         const auto kernelTsUs = getOldestPendingReadTs();
         if (baseline) {
-            // Updating baseline only on looper/epoll callback, i.e. on new set of pending reads.
+            // Updating baseline only on looper/epoll callback, i.e. on new set of pending
+            // reads.
             mHealthBase = {now, kernelTsUs};
         }
 
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a6cc946..cd6bfed 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -132,9 +132,7 @@
              std::string_view newPath);
     int unlink(StorageId storage, std::string_view path);
 
-    bool isRangeLoaded(StorageId storage, FileId file, std::pair<BlockIndex, BlockIndex> range) {
-        return false;
-    }
+    float getLoadingProgress(StorageId storage) const;
 
     RawMetadata getMetadata(StorageId storage, std::string_view path) const;
     RawMetadata getMetadata(StorageId storage, FileId node) const;
@@ -341,6 +339,8 @@
     int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
     binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
 
+    float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
+
     void registerAppOpsCallback(const std::string& packageName);
     bool unregisterAppOpsCallback(const std::string& packageName);
     void onAppOpChanged(const std::string& packageName);
@@ -363,6 +363,7 @@
     const std::unique_ptr<JniWrapper> mJni;
     const std::unique_ptr<LooperWrapper> mLooper;
     const std::unique_ptr<TimedQueueWrapper> mTimedQueue;
+    const std::unique_ptr<FsWrapper> mFs;
     const std::string mIncrementalDir;
 
     mutable std::mutex mLock;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 99a35ad..1ed46c4 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -25,6 +25,7 @@
 #include <binder/AppOpsManager.h>
 #include <utils/String16.h>
 
+#include <filesystem>
 #include <thread>
 
 #include "IncrementalServiceValidation.h"
@@ -165,6 +166,29 @@
     FileId getFileId(const Control& control, std::string_view path) const final {
         return incfs::getFileId(control, path);
     }
+    std::pair<IncFsBlockIndex, IncFsBlockIndex> countFilledBlocks(
+            const Control& control, std::string_view path) const final {
+        const auto fileId = incfs::getFileId(control, path);
+        const auto fd = incfs::openForSpecialOps(control, fileId);
+        int res = fd.get();
+        if (!fd.ok()) {
+            return {res, res};
+        }
+        const auto ranges = incfs::getFilledRanges(res);
+        res = ranges.first;
+        if (res) {
+            return {res, res};
+        }
+        const auto totalBlocksCount = ranges.second.internalRawRanges().endIndex;
+        int filledBlockCount = 0;
+        for (const auto& dataRange : ranges.second.dataRanges()) {
+            filledBlockCount += dataRange.size();
+        }
+        for (const auto& hashRange : ranges.second.hashRanges()) {
+            filledBlockCount += hashRange.size();
+        }
+        return {filledBlockCount, totalBlocksCount};
+    }
     ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final {
         return incfs::link(control, from, to);
     }
@@ -265,6 +289,23 @@
     std::thread mThread;
 };
 
+class RealFsWrapper : public FsWrapper {
+public:
+    RealFsWrapper() = default;
+    ~RealFsWrapper() = default;
+
+    std::vector<std::string> listFilesRecursive(std::string_view directoryPath) const final {
+        std::vector<std::string> files;
+        for (const auto& entry : std::filesystem::recursive_directory_iterator(directoryPath)) {
+            if (!entry.is_regular_file()) {
+                continue;
+            }
+            files.push_back(entry.path().c_str());
+        }
+        return files;
+    }
+};
+
 RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env)
       : mServiceManager(std::move(serviceManager)), mJvm(RealJniWrapper::getJvm(env)) {}
 
@@ -316,6 +357,10 @@
     return std::make_unique<RealTimedQueueWrapper>(mJvm);
 }
 
+std::unique_ptr<FsWrapper> RealServiceManager::getFs() {
+    return std::make_unique<RealFsWrapper>();
+}
+
 static JavaVM* getJavaVm(JNIEnv* env) {
     CHECK(env);
     JavaVM* jvm = nullptr;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 8cd726fd..82a1704 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -91,6 +91,8 @@
     virtual incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const = 0;
     virtual incfs::RawMetadata getMetadata(const Control& control, std::string_view path) const = 0;
     virtual FileId getFileId(const Control& control, std::string_view path) const = 0;
+    virtual std::pair<IncFsBlockIndex, IncFsBlockIndex> countFilledBlocks(
+            const Control& control, std::string_view path) const = 0;
     virtual ErrorCode link(const Control& control, std::string_view from,
                            std::string_view to) const = 0;
     virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
@@ -106,7 +108,8 @@
     virtual ~AppOpsManagerWrapper() = default;
     virtual binder::Status checkPermission(const char* permission, const char* operation,
                                            const char* package) const = 0;
-    virtual void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) = 0;
+    virtual void startWatchingMode(int32_t op, const String16& packageName,
+                                   const sp<IAppOpsCallback>& callback) = 0;
     virtual void stopWatchingMode(const sp<IAppOpsCallback>& callback) = 0;
 };
 
@@ -134,6 +137,12 @@
     virtual void stop() = 0;
 };
 
+class FsWrapper {
+public:
+    virtual ~FsWrapper() = default;
+    virtual std::vector<std::string> listFilesRecursive(std::string_view directoryPath) const = 0;
+};
+
 class ServiceManagerWrapper {
 public:
     virtual ~ServiceManagerWrapper() = default;
@@ -144,6 +153,7 @@
     virtual std::unique_ptr<JniWrapper> getJni() = 0;
     virtual std::unique_ptr<LooperWrapper> getLooper() = 0;
     virtual std::unique_ptr<TimedQueueWrapper> getTimedQueue() = 0;
+    virtual std::unique_ptr<FsWrapper> getFs() = 0;
 };
 
 // --- Real stuff ---
@@ -159,6 +169,7 @@
     std::unique_ptr<JniWrapper> getJni() final;
     std::unique_ptr<LooperWrapper> getLooper() final;
     std::unique_ptr<TimedQueueWrapper> getTimedQueue() final;
+    std::unique_ptr<FsWrapper> getFs() final;
 
 private:
     template <class INTERFACE>
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 1ae9e25..d1000e56 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -54,8 +54,10 @@
     MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
     MOCK_CONST_METHOD2(bindMount,
                        binder::Status(const std::string& sourceDir, const std::string& argetDir));
-    MOCK_CONST_METHOD2(setIncFsMountOptions,
-                       binder::Status(const ::android::os::incremental::IncrementalFileSystemControlParcel&, bool));
+    MOCK_CONST_METHOD2(
+            setIncFsMountOptions,
+            binder::Status(const ::android::os::incremental::IncrementalFileSystemControlParcel&,
+                           bool));
 
     void mountIncFsFails() {
         ON_CALL(*this, mountIncFs(_, _, _, _))
@@ -80,8 +82,8 @@
     }
     void setIncFsMountOptionsFails() const {
         ON_CALL(*this, setIncFsMountOptions(_, _))
-                .WillByDefault(
-                        Return(binder::Status::fromExceptionCode(1, String8("failed to set options"))));
+                .WillByDefault(Return(
+                        binder::Status::fromExceptionCode(1, String8("failed to set options"))));
     }
     void setIncFsMountOptionsSuccess() {
         ON_CALL(*this, setIncFsMountOptions(_, _)).WillByDefault(Return(binder::Status::ok()));
@@ -280,8 +282,12 @@
     MOCK_CONST_METHOD2(getMetadata, RawMetadata(const Control& control, FileId fileid));
     MOCK_CONST_METHOD2(getMetadata, RawMetadata(const Control& control, std::string_view path));
     MOCK_CONST_METHOD2(getFileId, FileId(const Control& control, std::string_view path));
+    MOCK_CONST_METHOD2(countFilledBlocks,
+                       std::pair<IncFsBlockIndex, IncFsBlockIndex>(const Control& control,
+                                                                   std::string_view path));
     MOCK_CONST_METHOD3(link,
-                       ErrorCode(const Control& control, std::string_view from, std::string_view to));
+                       ErrorCode(const Control& control, std::string_view from,
+                                 std::string_view to));
     MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
     MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
     MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
@@ -293,6 +299,19 @@
 
     void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
     void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
+
+    void countFilledBlocksSuccess() {
+        ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(1, 2)));
+    }
+
+    void countFilledBlocksFails() {
+        ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(-1, -1)));
+    }
+
+    void countFilledBlocksEmpty() {
+        ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(0, 0)));
+    }
+
     void openMountSuccess() {
         ON_CALL(*this, openMount(_)).WillByDefault(Invoke(this, &MockIncFs::openMountForHealth));
     }
@@ -447,6 +466,21 @@
     Job mWhat;
 };
 
+class MockFsWrapper : public FsWrapper {
+public:
+    MOCK_CONST_METHOD1(listFilesRecursive, std::vector<std::string>(std::string_view));
+    void hasNoFile() {
+        ON_CALL(*this, listFilesRecursive(_)).WillByDefault(Return(std::vector<std::string>()));
+    }
+    void hasFiles() {
+        ON_CALL(*this, listFilesRecursive(_))
+                .WillByDefault(Invoke(this, &MockFsWrapper::fakeFiles));
+    }
+    std::vector<std::string> fakeFiles(std::string_view directoryPath) {
+        return {"base.apk", "split.apk", "lib/a.so"};
+    }
+};
+
 class MockStorageHealthListener : public os::incremental::BnStorageHealthListener {
 public:
     MOCK_METHOD2(onHealthStatus, binder::Status(int32_t storageId, int32_t status));
@@ -474,23 +508,28 @@
                        std::unique_ptr<MockAppOpsManager> appOpsManager,
                        std::unique_ptr<MockJniWrapper> jni,
                        std::unique_ptr<MockLooperWrapper> looper,
-                       std::unique_ptr<MockTimedQueueWrapper> timedQueue)
+                       std::unique_ptr<MockTimedQueueWrapper> timedQueue,
+                       std::unique_ptr<MockFsWrapper> fs)
           : mVold(std::move(vold)),
             mDataLoaderManager(std::move(dataLoaderManager)),
             mIncFs(std::move(incfs)),
             mAppOpsManager(std::move(appOpsManager)),
             mJni(std::move(jni)),
             mLooper(std::move(looper)),
-            mTimedQueue(std::move(timedQueue)) {}
+            mTimedQueue(std::move(timedQueue)),
+            mFs(std::move(fs)) {}
     std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
     std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
         return std::move(mDataLoaderManager);
     }
     std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); }
-    std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final { return std::move(mAppOpsManager); }
+    std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final {
+        return std::move(mAppOpsManager);
+    }
     std::unique_ptr<JniWrapper> getJni() final { return std::move(mJni); }
     std::unique_ptr<LooperWrapper> getLooper() final { return std::move(mLooper); }
     std::unique_ptr<TimedQueueWrapper> getTimedQueue() final { return std::move(mTimedQueue); }
+    std::unique_ptr<FsWrapper> getFs() final { return std::move(mFs); }
 
 private:
     std::unique_ptr<MockVoldService> mVold;
@@ -500,6 +539,7 @@
     std::unique_ptr<MockJniWrapper> mJni;
     std::unique_ptr<MockLooperWrapper> mLooper;
     std::unique_ptr<MockTimedQueueWrapper> mTimedQueue;
+    std::unique_ptr<MockFsWrapper> mFs;
 };
 
 // --- IncrementalServiceTest ---
@@ -523,6 +563,8 @@
         mLooper = looper.get();
         auto timedQueue = std::make_unique<NiceMock<MockTimedQueueWrapper>>();
         mTimedQueue = timedQueue.get();
+        auto fs = std::make_unique<NiceMock<MockFsWrapper>>();
+        mFs = fs.get();
         mIncrementalService =
                 std::make_unique<IncrementalService>(MockServiceManager(std::move(vold),
                                                                         std::move(
@@ -531,12 +573,14 @@
                                                                         std::move(appOps),
                                                                         std::move(jni),
                                                                         std::move(looper),
-                                                                        std::move(timedQueue)),
+                                                                        std::move(timedQueue),
+                                                                        std::move(fs)),
                                                      mRootDir.path);
         mDataLoaderParcel.packageName = "com.test";
         mDataLoaderParcel.arguments = "uri";
         mDataLoaderManager->unbindFromDataLoaderSuccess();
         mIncrementalService->onSystemReady();
+        setupSuccess();
     }
 
     void setUpExistingMountDir(const std::string& rootDir) {
@@ -560,6 +604,14 @@
                 .WillByDefault(Invoke(mIncFs, &MockIncFs::getStorageMetadata));
     }
 
+    void setupSuccess() {
+        mVold->mountIncFsSuccess();
+        mIncFs->makeFileSuccess();
+        mVold->bindMountSuccess();
+        mDataLoaderManager->bindToDataLoaderSuccess();
+        mDataLoaderManager->getDataLoaderSuccess();
+    }
+
 protected:
     NiceMock<MockVoldService>* mVold = nullptr;
     NiceMock<MockIncFs>* mIncFs = nullptr;
@@ -568,6 +620,7 @@
     NiceMock<MockJniWrapper>* mJni = nullptr;
     NiceMock<MockLooperWrapper>* mLooper = nullptr;
     NiceMock<MockTimedQueueWrapper>* mTimedQueue = nullptr;
+    NiceMock<MockFsWrapper>* mFs = nullptr;
     NiceMock<MockDataLoader>* mDataLoader = nullptr;
     std::unique_ptr<IncrementalService> mIncrementalService;
     TemporaryDir mRootDir;
@@ -641,11 +694,6 @@
 }
 
 TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
@@ -661,11 +709,6 @@
 }
 
 TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
@@ -682,12 +725,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
@@ -705,12 +743,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
@@ -727,12 +760,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
@@ -748,14 +776,10 @@
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mIncFs->openMountSuccess();
     mIncFs->waitForPendingReadsSuccess();
-    mVold->bindMountSuccess();
+    mIncFs->openMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
+
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
@@ -776,12 +800,8 @@
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
     mIncFs->openMountSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
+
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
@@ -906,13 +926,9 @@
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mVold->setIncFsMountOptionsSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     mAppOpsManager->checkPermissionSuccess();
+
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     // We are calling setIncFsMountOptions(true).
@@ -930,13 +946,9 @@
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndDisabled) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mVold->setIncFsMountOptionsSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     mAppOpsManager->checkPermissionSuccess();
+
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     // Enabling and then disabling readlogs.
@@ -958,14 +970,10 @@
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mVold->setIncFsMountOptionsSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     mAppOpsManager->checkPermissionSuccess();
     mAppOpsManager->initializeStartWatchingMode();
+
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     // We are calling setIncFsMountOptions(true).
@@ -987,12 +995,8 @@
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     mAppOpsManager->checkPermissionFails();
+
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     // checkPermission fails, no calls to set opitions,  start or stop WatchingMode.
@@ -1008,13 +1012,9 @@
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
     mVold->setIncFsMountOptionsFails();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     mAppOpsManager->checkPermissionSuccess();
+
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     // We are calling setIncFsMountOptions.
@@ -1031,11 +1031,6 @@
 }
 
 TEST_F(IncrementalServiceTest, testMakeDirectory) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     TemporaryDir tempDir;
     int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
                                                        IncrementalService::CreateOptions::CreateNew,
@@ -1055,11 +1050,6 @@
 }
 
 TEST_F(IncrementalServiceTest, testMakeDirectories) {
-    mVold->mountIncFsSuccess();
-    mIncFs->makeFileSuccess();
-    mVold->bindMountSuccess();
-    mDataLoaderManager->bindToDataLoaderSuccess();
-    mDataLoaderManager->getDataLoaderSuccess();
     TemporaryDir tempDir;
     int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
                                                        IncrementalService::CreateOptions::CreateNew,
@@ -1078,4 +1068,51 @@
     auto res = mIncrementalService->makeDirs(storageId, dir_path, 0555);
     ASSERT_EQ(res, 0);
 }
+
+TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) {
+    mIncFs->countFilledBlocksSuccess();
+    mFs->hasNoFile();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId));
+}
+
+TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) {
+    mIncFs->countFilledBlocksFails();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+    ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId));
+}
+
+TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) {
+    mIncFs->countFilledBlocksEmpty();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
+    ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId));
+}
+
+TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) {
+    mIncFs->countFilledBlocksSuccess();
+    mFs->hasFiles();
+
+    TemporaryDir tempDir;
+    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                                       IncrementalService::CreateOptions::CreateNew,
+                                                       {}, {}, {});
+    EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
+    ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId));
+}
 } // namespace android::os::incremental
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/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index bc75dcd..12c69ea 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -150,7 +150,8 @@
         }
 
         // Sample for a fraction of app launches.
-        int traceFrequency = SystemProperties.getInt("profcollectd.applaunch_trace_freq", 2);
+        int traceFrequency =
+                SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         if (randomNum < traceFrequency) {
             try {
diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
index 4b25890..5fa809f 100644
--- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
+++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
@@ -204,10 +204,10 @@
                 CROSS_PROFILE_APP_PACKAGE_NAME, PERSONAL_PROFILE_UID, PERSONAL_PROFILE_USER_ID);
         ShadowApplicationPackageManager.setPackageUidAsUser(
                 CROSS_PROFILE_APP_PACKAGE_NAME, WORK_PROFILE_UID, WORK_PROFILE_USER_ID);
-        when(mPackageManagerInternal.getPackageUidInternal(
+        when(mPackageManagerInternal.getPackageUid(
                 CROSS_PROFILE_APP_PACKAGE_NAME, /* flags= */ 0, PERSONAL_PROFILE_USER_ID))
                 .thenReturn(PERSONAL_PROFILE_UID);
-        when(mPackageManagerInternal.getPackageUidInternal(
+        when(mPackageManagerInternal.getPackageUid(
                 CROSS_PROFILE_APP_PACKAGE_NAME, /* flags= */ 0, WORK_PROFILE_USER_ID))
                 .thenReturn(WORK_PROFILE_UID);
     }
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/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
index fdcadf3..d6894cf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -33,6 +33,7 @@
 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.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -85,7 +86,6 @@
 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;
 
@@ -484,7 +484,7 @@
                 PERMISSION_FINE, listener);
 
         CountDownLatch blocker = new CountDownLatch(1);
-        ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+        IN_PROCESS_EXECUTOR.execute(() -> {
             try {
                 blocker.await();
             } catch (InterruptedException e) {
@@ -622,7 +622,7 @@
                 PERMISSION_FINE, listener);
 
         CountDownLatch blocker = new CountDownLatch(1);
-        ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+        IN_PROCESS_EXECUTOR.execute(() -> {
             try {
                 blocker.await();
             } catch (InterruptedException e) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index 1ef1255..69a9f44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location.listeners;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -27,8 +29,6 @@
 import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertThrows;
 
-import android.location.util.identity.CallerIdentity;
-import android.os.Process;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -324,10 +324,8 @@
         boolean mActive = true;
 
         protected TestListenerRegistration(Integer integer,
-                Consumer<TestListenerRegistration> consumer,
-                boolean outOfProcess) {
-            super(integer, CallerIdentity.forTest(Process.myUid(),
-                    Process.myPid() + (outOfProcess ? 1 : 0), "test", "test"), consumer);
+                Consumer<TestListenerRegistration> consumer) {
+            super(DIRECT_EXECUTOR, integer, consumer);
         }
     }
 
@@ -345,7 +343,7 @@
         }
 
         public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
-            addRegistration(consumer, new TestListenerRegistration(request, consumer, true));
+            addRegistration(consumer, new TestListenerRegistration(request, consumer));
         }
 
         public void removeListener(Consumer<TestListenerRegistration> consumer) {
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 b7a36f2..8d4f2aa 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -20,12 +20,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.gt;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.intThat;
-import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
@@ -167,7 +168,7 @@
     @Test
     public void createService_initializesNativeService() {
         createService();
-        verify(mNativeWrapperMock).vibratorInit();
+        verify(mNativeWrapperMock).vibratorInit(notNull());
         verify(mNativeWrapperMock).vibratorOff();
     }
 
@@ -294,7 +295,7 @@
         assertTrue(service.isVibrating());
 
         verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
+        verify(mNativeWrapperMock).vibratorOn(eq(100L), gt(0L));
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(128));
     }
 
@@ -307,7 +308,7 @@
         assertTrue(service.isVibrating());
 
         verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
+        verify(mNativeWrapperMock).vibratorOn(eq(100L), gt(0L));
         verify(mNativeWrapperMock, never()).vibratorSetAmplitude(anyInt());
     }
 
@@ -321,10 +322,8 @@
         vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
 
         verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorPerformEffect(
-                eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
-                any(VibratorService.Vibration.class));
+        verify(mNativeWrapperMock).vibratorPerformEffect(eq((long) VibrationEffect.EFFECT_CLICK),
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), gt(0L));
     }
 
     @Test
@@ -343,7 +342,7 @@
 
         verify(mNativeWrapperMock).vibratorOff();
         verify(mNativeWrapperMock).vibratorPerformComposedEffect(
-                primitivesCaptor.capture(), any(VibratorService.Vibration.class));
+                primitivesCaptor.capture(), gt(0L));
 
         // Check all primitive effect fields are passed down to the HAL.
         assertEquals(1, primitivesCaptor.getValue().length);
@@ -368,7 +367,7 @@
 
         // Wait for VibrateThread to turn vibrator ON with total timing and no callback.
         Thread.sleep(5);
-        verify(mNativeWrapperMock).vibratorOn(eq(30L), isNull());
+        verify(mNativeWrapperMock).vibratorOn(eq(30L), eq(0L));
 
         // First amplitude set right away.
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
@@ -384,11 +383,11 @@
 
     @Test
     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();
+        doAnswer(invocation -> {
+            service.onVibrationComplete(invocation.getArgument(1));
+            return null;
+        }).when(mNativeWrapperMock).vibratorOn(anyLong(), anyLong());
         Mockito.clearInvocations(mNativeWrapperMock);
 
         vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
@@ -396,7 +395,7 @@
         InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
         inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
         inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L),
-                any(VibratorService.Vibration.class));
+                gt(0L));
         inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
@@ -404,12 +403,11 @@
     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();
+        doAnswer(invocation -> {
+            service.onVibrationComplete(invocation.getArgument(2));
+            return 10_000L; // 10s
+        }).when(mNativeWrapperMock).vibratorPerformEffect(anyLong(), anyLong(), anyLong());
         Mockito.clearInvocations(mNativeWrapperMock);
 
         vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
@@ -419,7 +417,7 @@
         inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
                 eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
-                any(VibratorService.Vibration.class));
+                gt(0L));
         inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
@@ -436,20 +434,19 @@
         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).vibratorOn(eq(3L), eq(0L));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), eq(0L));
         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();
+        doAnswer(invocation -> {
+            service.onVibrationComplete(invocation.getArgument(1));
+            return null;
+        }).when(mNativeWrapperMock).vibratorPerformComposedEffect(any(), anyLong());
         Mockito.clearInvocations(mNativeWrapperMock);
 
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -460,32 +457,7 @@
         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);
-        doAnswer(invocation -> {
-            ((VibratorService.Vibration) invocation.getArgument(1)).binderDied();
-            return null;
-        }).when(mNativeWrapperMock).vibratorPerformComposedEffect(
-                any(), any(VibratorService.Vibration.class));
-        VibratorService service = createService();
-        Mockito.clearInvocations(mNativeWrapperMock);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
-                .compose();
-        vibrate(service, effect);
-
-        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
-        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
-        inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
-                any(VibrationEffect.Composition.PrimitiveEffect[].class),
-                any(VibratorService.Vibration.class));
+                any(VibrationEffect.Composition.PrimitiveEffect[].class), gt(0L));
         inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
@@ -513,12 +485,11 @@
 
     @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();
-
+        doAnswer(invocation -> {
+            service.onVibrationComplete(invocation.getArgument(1));
+            return null;
+        }).when(mNativeWrapperMock).vibratorOn(anyLong(), anyLong());
         service.registerVibratorStateListener(mVibratorStateListenerMock);
         verify(mVibratorStateListenerMock).onVibrating(false);
         Mockito.clearInvocations(mVibratorStateListenerMock);
@@ -569,15 +540,15 @@
 
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), anyLong());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_TICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), anyLong());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), anyLong());
         verify(mNativeWrapperMock, never()).vibratorPerformEffect(
-                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any());
+                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), anyLong());
     }
 
     @Test
@@ -644,7 +615,7 @@
 
         // Ringtone vibration is off, so only the other 3 are propagated to native.
         verify(mNativeWrapperMock, times(3)).vibratorPerformComposedEffect(
-                primitivesCaptor.capture(), any());
+                primitivesCaptor.capture(), anyLong());
 
         List<VibrationEffect.Composition.PrimitiveEffect[]> values =
                 primitivesCaptor.getAllValues();
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 763654d..dda81ff 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
@@ -187,7 +187,7 @@
         moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
-        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
+        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
     }
 
     @Test
@@ -288,7 +288,7 @@
         assertState(STATE_DRAGGING);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertState(STATE_CLEAR);
-        assertCapturedEvents(ACTION_DOWN, ACTION_UP);
+        assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
         assertCapturedEventsNoHistory();
     }
 
@@ -301,6 +301,7 @@
         assertState(STATE_CLEAR);
         assertCapturedEvents(
                 /* goto dragging state */ ACTION_DOWN,
+                ACTION_MOVE,
                 /* leave dragging state */ ACTION_UP,
                 ACTION_DOWN,
                 ACTION_POINTER_DOWN,
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..a10e0ba 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,20 +76,12 @@
                 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));
     }
 
     @After
     public void tearDown() {
-        mWindowMagnificationManager.disableWindowMagnifier(DISPLAY_0, true);
+        mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, true);
     }
 
     @Test
@@ -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: {
@@ -239,11 +225,12 @@
             }
             break;
             case STATE_SHOW_MAGNIFIER: {
-                mWindowMagnificationManager.disableWindowMagnifier(DISPLAY_0, false);
+                mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, false);
             }
             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/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 70e6a34..e067b7e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -68,8 +68,10 @@
     private static final int CURRENT_USER_ID = UserHandle.USER_CURRENT;
 
     private MockWindowMagnificationConnection mMockConnection;
-    @Mock private Context mContext;
-    @Mock private StatusBarManagerInternal mMockStatusBarManagerInternal;
+    @Mock
+    private Context mContext;
+    @Mock
+    private StatusBarManagerInternal mMockStatusBarManagerInternal;
     private MockContentResolver mResolver;
     private WindowMagnificationManager mWindowMagnificationManager;
 
@@ -84,7 +86,7 @@
 
         when(mContext.getContentResolver()).thenReturn(mResolver);
         doAnswer((InvocationOnMock invocation) -> {
-            final boolean connect =  (Boolean) invocation.getArguments()[0];
+            final boolean connect = (Boolean) invocation.getArguments()[0];
             mWindowMagnificationManager.setConnection(
                     connect ? mMockConnection.getConnection() : null);
             return null;
@@ -161,7 +163,7 @@
     public void enable_TestDisplay_enableWindowMagnification() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
 
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2f, 200f, 300f);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f);
 
         verify(mMockConnection.getConnection()).enableWindowMagnification(TEST_DISPLAY, 2f,
                 200f, 300f);
@@ -170,9 +172,9 @@
     @Test
     public void disable_testDisplay_disableWindowMagnification() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 3f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
 
-        mWindowMagnificationManager.disableWindowMagnifier(TEST_DISPLAY, false);
+        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
 
         verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY);
     }
@@ -183,7 +185,7 @@
 
         assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
 
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
 
         assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
     }
@@ -198,7 +200,7 @@
     @Test
     public void persistScale_setValue_expectedValueInProvider() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2.0f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
         mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f);
 
         mWindowMagnificationManager.persistScale(TEST_DISPLAY);
@@ -211,7 +213,7 @@
     @Test
     public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2.0f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
 
         mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f);
 
@@ -221,7 +223,7 @@
     @Test
     public void scaleSetterGetter_scaleIsOutOfRang_getNormalizeValue() {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2.5f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
 
         mWindowMagnificationManager.setScale(TEST_DISPLAY, 10.0f);
 
@@ -232,9 +234,9 @@
     @Test
     public void moveWindowMagnifier() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 2f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
 
-        mWindowMagnificationManager.moveWindowMagnifier(TEST_DISPLAY, 200, 300);
+        mWindowMagnificationManager.moveWindowMagnification(TEST_DISPLAY, 200, 300);
         verify(mMockConnection.getConnection()).moveWindowMagnifier(TEST_DISPLAY, 200, 300);
     }
 
@@ -254,7 +256,7 @@
     @Test
     public void pointersInWindow_returnCorrectValue() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 3.0f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
         mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(TEST_DISPLAY,
                 new Rect(0, 0, 500, 500));
         PointF[] pointersLocation = new PointF[2];
@@ -268,7 +270,7 @@
     @Test
     public void binderDied_windowMagnifierIsEnabled_resetState() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 3f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
 
         mMockConnection.getDeathRecipient().binderDied();
 
@@ -280,7 +282,7 @@
             requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
             throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnifier(TEST_DISPLAY, 3f, NaN, NaN);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
 
         assertTrue(mWindowMagnificationManager.requestConnection(false));
 
@@ -306,21 +308,24 @@
     @Test
     public void requestConnection_registerAndUnregisterBroadcastReceiver() {
         assertTrue(mWindowMagnificationManager.requestConnection(true));
-        verify(mContext).registerReceiver(any(BroadcastReceiver.class),  any(IntentFilter.class));
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
 
         assertTrue(mWindowMagnificationManager.requestConnection(false));
         verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
     }
 
     @Test
-    public void onReceiveScreenOff_removeMagnificationButtonAndDisableWindowMagnification()
+    public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification()
             throws RemoteException {
         mWindowMagnificationManager.requestConnection(true);
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
+
         mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext,
                 new Intent(Intent.ACTION_SCREEN_OFF));
 
         verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
         verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY);
+        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
     }
 
     private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
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/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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7f6723e..e6fc792 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2624,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());
@@ -4465,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())
@@ -5694,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/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index b306ff0..431cc27 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -16,7 +16,6 @@
 package com.android.server.devicepolicy;
 
 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.RETURNS_DEEP_STUBS;
@@ -236,7 +235,7 @@
         }
         mUserInfos.add(uh);
         when(userManager.getUsers()).thenReturn(mUserInfos);
-        when(userManager.getUsers(anyBoolean())).thenReturn(mUserInfos);
+        when(userManager.getAliveUsers()).thenReturn(mUserInfos);
         when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(true);
         when(userManager.getProfileParent(anyInt())).thenAnswer(
                 invocation -> {
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 ef2365e..3f324a2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -693,6 +694,82 @@
     }
 
     @Test
+    public void sendVolumeKeyEvent_toTv_activeSource() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiControlService.setSystemAudioActivated(false);
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage pressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage released = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+    }
+
+    @Test
+    public void sendVolumeKeyEvent_toAudio_activeSource() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiControlService.setSystemAudioActivated(true);
+        mHdmiControlService.setActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage pressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage released = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+    }
+
+    @Test
+    public void sendVolumeKeyEvent_toTv_inactiveSource() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiControlService.setSystemAudioActivated(false);
+        mHdmiControlService.setActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage pressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage released = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+    }
+
+    @Test
+    public void sendVolumeKeyEvent_toAudio_inactiveSource() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiControlService.setSystemAudioActivated(true);
+        mHdmiControlService.setActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage pressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage released = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+    }
+
+    @Test
     public void handleSetStreamPath_broadcastsActiveSource() {
         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
                 mPlaybackPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index 53c4d6f..f17173f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -62,6 +62,32 @@
         assertThat(message).isEqualTo(buildMessage("5F:81:21:00"));
     }
 
+    @Test
+    public void buildSetOsdName_short() {
+        String deviceName = "abc";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(buildMessage("40:47:61:62:63"));
+    }
+
+    @Test
+    public void buildSetOsdName_maximumLength() {
+        String deviceName = "abcdefghijklmn";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(
+                buildMessage("40:47:61:62:63:64:65:66:67:68:69:6A:6B:6C:6D:6E"));
+    }
+
+    @Test
+    public void buildSetOsdName_tooLong() {
+        String deviceName = "abcdefghijklmnop";
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildSetOsdNameCommand(ADDR_PLAYBACK_1,
+                ADDR_TV, deviceName);
+        assertThat(message).isEqualTo(
+                buildMessage("40:47:61:62:63:64:65:66:67:68:69:6A:6B:6C:6D:6E"));
+    }
+
     /**
      * Build a CEC message from a hex byte string with bytes separated by {@code :}.
      *
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/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 f96ebda..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,26 +17,20 @@
 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
-import org.junit.rules.Timeout
-import java.util.concurrent.TimeUnit
 
 /**
  * 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
-    val timeout = Timeout(4, TimeUnit.MINUTES)
-
-    @get:Rule
     val expect = Expect.create()
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
new file mode 100644
index 0000000..d7ed96f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.TimeZoneCapabilities;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
+ * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ */
+public class ConfigurationInternalTest {
+
+    private static final int ARBITRARY_USER_ID = 99999;
+
+    /**
+     * Tests when {@link ConfigurationInternal#isUserConfigAllowed()} and
+     * {@link ConfigurationInternal#isAutoDetectionSupported()} are both true.
+     */
+    @Test
+    public void test_unrestricted() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionSupported(true)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+
+    /** Tests when {@link ConfigurationInternal#isUserConfigAllowed()} is false */
+    @Test
+    public void test_restricted() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(false)
+                .setAutoDetectionSupported(true)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+
+    /** Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is false. */
+    @Test
+    public void test_autoDetectNotSupported() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionSupported(false)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
+            assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
+            assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
+            assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
+            assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+        }
+    }
+}
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 e5e9311..4ef2082 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -32,56 +33,64 @@
 
 class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
-    private StrategyListener mListener;
+    private ConfigurationChangeListener mConfigurationChangeListener;
 
     // Fake state
-    private TimeZoneCapabilities mCapabilities;
-    private TimeZoneConfiguration mConfiguration;
+    private ConfigurationInternal mConfigurationInternal;
 
     // Call tracking.
     private GeolocationTimeZoneSuggestion mLastGeolocationSuggestion;
     private ManualTimeZoneSuggestion mLastManualSuggestion;
     private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
-    private boolean mHandleAutoTimeZoneConfigChangedCalled;
     private boolean mDumpCalled;
     private final List<Dumpable> mDumpables = new ArrayList<>();
 
     @Override
-    public void setStrategyListener(@NonNull StrategyListener listener) {
-        mListener = listener;
+    public void addConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+        if (mConfigurationChangeListener != null) {
+            fail("Fake only supports one listener");
+        }
+        mConfigurationChangeListener = listener;
     }
 
     @Override
-    public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-        return mCapabilities;
+    public ConfigurationInternal getConfigurationInternal(int userId) {
+        if (mConfigurationInternal.getUserId() != userId) {
+            fail("Fake only supports one user");
+        }
+        return mConfigurationInternal;
     }
 
     @Override
-    public boolean updateConfiguration(
-            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
-        assertNotNull(mConfiguration);
-        assertNotNull(configuration);
+    public ConfigurationInternal getCurrentUserConfigurationInternal() {
+        return mConfigurationInternal;
+    }
 
-        // Simulate the strategy's behavior: the new configuration will be the old configuration
-        // merged with the new.
-        TimeZoneConfiguration oldConfiguration = mConfiguration;
-        TimeZoneConfiguration newConfiguration =
-                new TimeZoneConfiguration.Builder(mConfiguration)
-                        .mergeProperties(configuration)
-                        .build();
+    @Override
+    public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
+        assertNotNull(mConfigurationInternal);
+        assertNotNull(requestedChanges);
 
-        if (newConfiguration.equals(oldConfiguration)) {
+        // Simulate the real strategy's behavior: the new configuration will be updated to be the
+        // old configuration merged with the new if the user has the capability to up the settings.
+        // Then, if the configuration changed, the change listener is invoked.
+        TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
+        TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+        if (newConfiguration == null) {
             return false;
         }
-        mConfiguration = newConfiguration;
-        mListener.onConfigurationChanged();
+
+        if (!newConfiguration.equals(capabilities.getConfiguration())) {
+            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
+
+            // Note: Unlike the real strategy, the listeners is invoked synchronously.
+            mConfigurationChangeListener.onChange();
+        }
         return true;
     }
 
-    @Override
-    @NonNull
-    public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-        return mConfiguration;
+    public void simulateConfigurationChangeForTests() {
+        mConfigurationChangeListener.onChange();
     }
 
     @Override
@@ -103,11 +112,6 @@
     }
 
     @Override
-    public void handleAutoTimeZoneConfigChanged() {
-        mHandleAutoTimeZoneConfigChangedCalled = true;
-    }
-
-    @Override
     public void addDumpable(Dumpable dumpable) {
         mDumpables.add(dumpable);
     }
@@ -117,19 +121,14 @@
         mDumpCalled = true;
     }
 
-    void initializeConfiguration(TimeZoneConfiguration configuration) {
-        mConfiguration = configuration;
-    }
-
-    void initializeCapabilities(TimeZoneCapabilities capabilities) {
-        mCapabilities = capabilities;
+    void initializeConfiguration(ConfigurationInternal configurationInternal) {
+        mConfigurationInternal = configurationInternal;
     }
 
     void resetCallTracking() {
         mLastGeolocationSuggestion = null;
         mLastManualSuggestion = null;
         mLastTelephonySuggestion = null;
-        mHandleAutoTimeZoneConfigChangedCalled = false;
         mDumpCalled = false;
     }
 
@@ -146,10 +145,6 @@
         assertEquals(expectedSuggestion, mLastTelephonySuggestion);
     }
 
-    void verifyHandleAutoTimeZoneConfigChangedCalled() {
-        assertTrue(mHandleAutoTimeZoneConfigChangedCalled);
-    }
-
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
new file mode 100644
index 0000000..f45b3a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
@@ -0,0 +1,53 @@
+/*
+ * 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.UserIdInt;
+
+/** A fake {@link CallerIdentityInjector} used in tests. */
+public class TestCallerIdentityInjector implements CallerIdentityInjector {
+
+    private long mToken = 9999L;
+    private int mCallingUserId;
+    private Integer mCurrentCallingUserId;
+
+    public void initializeCallingUserId(@UserIdInt int userId) {
+        mCallingUserId = userId;
+        mCurrentCallingUserId = userId;
+    }
+
+    @Override
+    public int getCallingUserId() {
+        assertNotNull("callingUserId has been cleared", mCurrentCallingUserId);
+        return mCurrentCallingUserId;
+    }
+
+    @Override
+    public long clearCallingIdentity() {
+        mCurrentCallingUserId = null;
+        return mToken;
+    }
+
+    @Override
+    public void restoreCallingIdentity(long token) {
+        assertEquals(token, mToken);
+        mCurrentCallingUserId = mCallingUserId;
+    }
+}
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 e9d57e5..918babc 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.timezonedetector;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
@@ -85,6 +86,18 @@
         mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
     }
 
+    @Test
+    public void testAddConfigurationListener() throws Exception {
+        boolean[] changeCalled = new boolean[2];
+        mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[0] = true);
+        mTimeZoneDetectorInternal.addConfigurationListener(() -> changeCalled[1] = true);
+
+        mFakeTimeZoneDetectorStrategy.simulateConfigurationChangeForTests();
+
+        assertTrue(changeCalled[0]);
+        assertTrue(changeCalled[1]);
+    }
+
     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 3a1ec4f..27b04b6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -36,7 +34,6 @@
 import android.app.timezonedetector.ITimeZoneConfigurationListener;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -65,6 +62,7 @@
     private TimeZoneDetectorService mTimeZoneDetectorService;
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
+    private TestCallerIdentityInjector mTestCallerIdentityInjector;
 
 
     @Before
@@ -76,10 +74,14 @@
         mHandlerThread.start();
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
 
+        mTestCallerIdentityInjector = new TestCallerIdentityInjector();
+        mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
+
         mFakeTimeZoneDetectorStrategy = new FakeTimeZoneDetectorStrategy();
 
         mTimeZoneDetectorService = new TimeZoneDetectorService(
-                mMockContext, mTestHandler, mFakeTimeZoneDetectorStrategy);
+                mMockContext, mTestHandler, mTestCallerIdentityInjector,
+                mFakeTimeZoneDetectorStrategy);
     }
 
     @After
@@ -107,40 +109,12 @@
     public void testGetCapabilities() {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        TimeZoneCapabilities capabilities = createTimeZoneCapabilities();
-        mFakeTimeZoneDetectorStrategy.initializeCapabilities(capabilities);
-
-        assertEquals(capabilities, mTimeZoneDetectorService.getCapabilities());
-
-        verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
-                anyString());
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testGetConfiguration_withoutPermission() {
-        doThrow(new SecurityException("Mock"))
-                .when(mMockContext).enforceCallingPermission(anyString(), any());
-
-        try {
-            mTimeZoneDetectorService.getConfiguration();
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
-                    anyString());
-        }
-    }
-
-    @Test
-    public void testGetConfiguration() {
-        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
-
-        TimeZoneConfiguration configuration =
-                createTimeZoneConfiguration(false /* autoDetectionEnabled */);
+        ConfigurationInternal configuration =
+                createConfigurationInternal(true /* autoDetectionEnabled*/);
         mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
 
-        assertEquals(configuration, mTimeZoneDetectorService.getConfiguration());
+        assertEquals(configuration.createCapabilities(),
+                mTimeZoneDetectorService.getCapabilities());
 
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
@@ -181,10 +155,9 @@
 
     @Test
     public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
-        TimeZoneConfiguration autoDetectDisabledConfiguration =
-                createTimeZoneConfiguration(false /* autoDetectionEnabled */);
-
-        mFakeTimeZoneDetectorStrategy.initializeConfiguration(autoDetectDisabledConfiguration);
+        ConfigurationInternal initialConfiguration =
+                createConfigurationInternal(false /* autoDetectionEnabled */);
+        mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
@@ -210,13 +183,12 @@
             // Simulate the configuration being changed and verify the mockListener was notified.
             TimeZoneConfiguration autoDetectEnabledConfiguration =
                     createTimeZoneConfiguration(true /* autoDetectionEnabled */);
-
             mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
                     anyString());
-            verify(mockListener).onChange(autoDetectEnabledConfiguration);
+            verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
@@ -242,12 +214,14 @@
         {
             doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
+            TimeZoneConfiguration autoDetectDisabledConfiguration =
+                    createTimeZoneConfiguration(false /* autoDetectionEnabled */);
             mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
                     anyString());
-            verify(mockListener, never()).onChange(any());
+            verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
@@ -379,33 +353,22 @@
         mFakeTimeZoneDetectorStrategy.verifyDumpCalled();
     }
 
-    @Test
-    public void testHandleAutoTimeZoneConfigChanged() throws Exception {
-        mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
-        mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
-
-        mFakeTimeZoneDetectorStrategy.resetCallTracking();
-
-        mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
-        mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mFakeTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneConfigChangedCalled();
-    }
-
-    private static TimeZoneConfiguration createTimeZoneConfiguration(
-            boolean autoDetectionEnabled) {
-        return new TimeZoneConfiguration.Builder()
+    private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
+        return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
                 .setAutoDetectionEnabled(autoDetectionEnabled)
                 .build();
     }
 
-    private static TimeZoneCapabilities createTimeZoneCapabilities() {
-        return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
-                .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
-                .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
+    private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
+        // Default geo detection settings from auto detection settings - they are not important to
+        // the tests.
+        final boolean geoDetectionEnabled = autoDetectionEnabled;
+        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setAutoDetectionSupported(true)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionEnabled(autoDetectionEnabled)
+                .setLocationEnabled(geoDetectionEnabled)
+                .setGeoDetectionEnabled(geoDetectionEnabled)
                 .build();
     }
 
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 a6caa42..2bee5e5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -23,10 +23,6 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
@@ -37,9 +33,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -47,7 +43,6 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneCapabilities;
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
@@ -61,7 +56,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -94,192 +88,149 @@
                     TELEPHONY_SCORE_HIGHEST),
     };
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
-            new TimeZoneConfiguration.Builder()
-                    .setAutoDetectionEnabled(true)
-                    .build();
-
-    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
-            new TimeZoneConfiguration.Builder()
+    private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionSupported(true)
                     .setAutoDetectionEnabled(false)
-                    .build();
-
-    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
-            new TimeZoneConfiguration.Builder()
+                    .setLocationEnabled(true)
                     .setGeoDetectionEnabled(false)
                     .build();
 
-    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
-            new TimeZoneConfiguration.Builder()
+    private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
                     .setGeoDetectionEnabled(true)
                     .build();
 
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(false)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(true)
+                    .setAutoDetectionEnabled(false)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setAutoDetectionSupported(true)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setAutoDetectionSupported(true)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
+    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
+            createConfig(false /* autoDetection */, null);
+    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
+            createConfig(true /* autoDetection */, null);
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+            createConfig(null, true /* geoDetection */);
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+            createConfig(null, false /* geoDetection */);
+
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeCallback mFakeCallback;
-    private MockStrategyListener mMockStrategyListener;
+    private MockConfigChangeListener mMockConfigChangeListener;
+
 
     @Before
     public void setUp() {
         mFakeCallback = new FakeCallback();
-        mMockStrategyListener = new MockStrategyListener();
+        mMockConfigChangeListener = new MockConfigChangeListener();
         mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeCallback);
-        mFakeCallback.setStrategyForSettingsCallbacks(mTimeZoneDetectorStrategy);
-        mTimeZoneDetectorStrategy.setStrategyListener(mMockStrategyListener);
+        mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
     }
 
     @Test
-    public void testGetCapabilities() {
-        new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
-        assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
-    }
-
-    @Test
-    public void testGetConfiguration() {
-        new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
-        TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
-        assertTrue(expectedConfiguration.isComplete());
-        assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_unrestricted() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                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_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());
-        }
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_restricted() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                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_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());
-        }
-    }
-
-    @Test
-    public void testCapabilitiesTestInfra_autoDetectNotSupported() {
-        Script script = new Script();
-
-        script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                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_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());
-        }
+    public void testGetCurrentUserConfiguration() {
+        new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
+        ConfigurationInternal expectedConfiguration =
+                mFakeCallback.getConfigurationInternal(USER_ID);
+        assertEquals(expectedConfiguration,
+                mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
     }
 
     @Test
     public void testUpdateConfiguration_unrestricted() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Set the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(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_DISABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID,
-                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_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_DISABLED));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
+                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));
+        script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
     }
 
     @Test
     public void testUpdateConfiguration_restricted() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(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_ENABLED,  false /* expectedResult */);
+        script.simulateUpdateConfiguration(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.
+        // Try to  update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
+                CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -287,20 +238,16 @@
 
     @Test
     public void testUpdateConfiguration_autoDetectNotSupported() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(
-                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(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_ENABLED, false /* expectedResult */);
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -313,8 +260,7 @@
         TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
                 createEmptySlotIndex2Suggestion();
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -359,9 +305,7 @@
         TelephonyTestCase testCase2 = newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
                 QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
 
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // A low quality suggestions will not be taken: The device time zone setting is left
         // uninitialized.
@@ -426,8 +370,7 @@
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
             // Start with the device in a known state.
-            script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                    CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+            script.initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                     .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
             TelephonyTimeZoneSuggestion suggestion =
@@ -447,8 +390,7 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting on should cause the device setting to be set.
-            script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
-                    true /* expectedResult */);
+            script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
             // When time zone detection is already enabled the suggestion (if it scores highly
             // enough) should be set immediately.
@@ -465,8 +407,7 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting should off should do nothing.
-            script.simulateUpdateConfiguration(
-                    USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+            script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
@@ -480,8 +421,7 @@
     @Test
     public void testTelephonySuggestionsSingleSlotId() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -546,8 +486,7 @@
                         TELEPHONY_SCORE_NONE);
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_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.
@@ -632,9 +571,7 @@
      */
     @Test
     public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         TelephonyTestCase testCase = newTelephonyTestCase(
                 MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -652,40 +589,39 @@
 
         // 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.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
-                .simulateUpdateConfiguration(
-                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .simulateUpdateConfiguration(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 */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
                 .verifyTimeZoneNotChanged();
         // Latest suggestion should be used.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+        script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(newYorkSuggestion);
     }
 
     @Test
-    public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
-        checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+    public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoTelephony() {
+        checkManualSuggestion_unrestricted_autoDetectionEnabled(false /* geoDetectionEnabled */);
     }
 
     @Test
-    public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
-        checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+    public void testManualSuggestion_unrestricted_autoDetectionEnabled_autoGeo() {
+        checkManualSuggestion_unrestricted_autoDetectionEnabled(true /* geoDetectionEnabled */);
     }
 
-    private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
-        TimeZoneConfiguration geoTzEnabledConfig =
-                new TimeZoneConfiguration.Builder()
+    private void checkManualSuggestion_unrestricted_autoDetectionEnabled(
+            boolean geoDetectionEnabled) {
+        ConfigurationInternal geoTzEnabledConfig =
+                new ConfigurationInternal.Builder(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED)
                         .setGeoDetectionEnabled(geoDetectionEnabled)
                         .build();
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
+                .initializeConfig(geoTzEnabledConfig)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -697,35 +633,19 @@
     @Test
     public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
-        // Auto time zone detection is enabled so the manual suggestion should be ignored.
+        // User is restricted so the manual suggestion should be ignored.
         script.simulateManualTimeZoneSuggestion(
                 USER_ID, createManualSuggestion("Europe/Paris"), false /* expectedResult */)
-            .verifyTimeZoneNotChanged();
-    }
-
-    @Test
-    public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        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.
-        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
-        script.simulateManualTimeZoneSuggestion(
-                USER_ID, manualSuggestion, true /* expectedResult */)
-            .verifyTimeZoneChangedAndReset(manualSuggestion);
+                .verifyTimeZoneNotChanged();
     }
 
     @Test
     public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is disabled so the manual suggestion should be used.
@@ -738,8 +658,7 @@
     @Test
     public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Restricted users do not have the capability.
@@ -750,10 +669,9 @@
     }
 
     @Test
-    public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
+    public void testManualSuggestion_autoDetectNotSupported() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Unrestricted users have the capability.
@@ -765,9 +683,7 @@
 
     @Test
     public void testGeoSuggestion_uncertain() {
-        Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
@@ -783,8 +699,7 @@
     @Test
     public void testGeoSuggestion_noZones() {
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
@@ -802,8 +717,7 @@
                 createGeoLocationSuggestion(list("Europe/London"));
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateGeolocationTimeZoneSuggestion(suggestion)
@@ -828,8 +742,7 @@
                 createGeoLocationSuggestion(list("Europe/Paris"));
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
@@ -856,72 +769,27 @@
                 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 =
+    public void testGeoSuggestion_togglingGeoDetectionClearsLastSuggestion() {
+        GeolocationTimeZoneSuggestion suggestion =
                 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))
+                .initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_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 */)
+        script.simulateGeolocationTimeZoneSuggestion(suggestion)
                 .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();
+        // Assert internal service state.
+        assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
 
-        // 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();
+        // Turn off geo detection and verify the latest suggestion is cleared.
+        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+                .verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
 
         // Assert internal service state.
-        assertEquals(uncertainGeolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
     }
 
     /**
@@ -937,88 +805,48 @@
                 "Europe/Paris");
 
         Script script = new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Add suggestions. Nothing should happen as time zone detection is disabled.
         script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
                 .verifyTimeZoneNotChanged();
+
+        // Geolocation suggestions are only stored when geolocation detection is enabled.
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
         script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
                 .verifyTimeZoneNotChanged();
 
-        // Assert internal service state.
-        assertEquals(geolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        // Telephony suggestions are always stored.
         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 */)
+        script.simulateUpdateConfiguration(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 */)
+        // Changing the detection to enable geo detection won't cause the device tz setting to
+        // change because the geo suggestion is empty.
+        script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
                 .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 */)
+        script.simulateUpdateConfiguration(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");
+        assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
     }
 
     @Test
     public void testAddDumpable() {
         new Script()
-                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeConfig(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -1069,25 +897,26 @@
         return suggestion;
     }
 
+    private static TimeZoneConfiguration createConfig(
+            @Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
+        TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+        if (autoDetection != null) {
+            builder.setAutoDetectionEnabled(autoDetection);
+        }
+        if (geoDetection != null) {
+            builder.setGeoDetectionEnabled(geoDetection);
+        }
+        return builder.build();
+    }
+
     static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
 
-        private TimeZoneCapabilities mCapabilities;
-        private final TestState<UserConfiguration> mConfiguration = new TestState<>();
+        private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
         private final TestState<String> mTimeZoneId = new TestState<>();
-        private TimeZoneDetectorStrategyImpl mStrategy;
+        private ConfigurationChangeListener mConfigChangeListener;
 
-        void setStrategyForSettingsCallbacks(TimeZoneDetectorStrategyImpl strategy) {
-            assertNotNull(strategy);
-            mStrategy = strategy;
-        }
-
-        void initializeUser(@UserIdInt int userId, TimeZoneCapabilities capabilities,
-                TimeZoneConfiguration configuration) {
-            assertEquals(userId, capabilities.getUserId());
-            mCapabilities = capabilities;
-            assertTrue("Configuration must be complete when initializing, config=" + configuration,
-                    configuration.isComplete());
-            mConfiguration.init(new UserConfiguration(userId, configuration));
+        void initializeConfig(ConfigurationInternal configurationInternal) {
+            mConfigurationInternal.init(configurationInternal);
         }
 
         void initializeTimeZoneSetting(String zoneId) {
@@ -1095,43 +924,22 @@
         }
 
         @Override
-        public TimeZoneCapabilities getCapabilities(@UserIdInt int userId) {
-            assertEquals(userId, mCapabilities.getUserId());
-            return mCapabilities;
+        public void setConfigChangeListener(ConfigurationChangeListener listener) {
+            mConfigChangeListener = listener;
         }
 
         @Override
-        public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
-            UserConfiguration latest = mConfiguration.getLatest();
-            assertEquals(userId, latest.userId);
-            return latest.configuration;
-        }
-
-        @Override
-        public void setConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfig) {
-            assertNotNull(newConfig);
-            assertTrue(newConfig.isComplete());
-
-            UserConfiguration latestUserConfig = mConfiguration.getLatest();
-            assertEquals(userId, latestUserConfig.userId);
-            TimeZoneConfiguration oldConfig = latestUserConfig.configuration;
-
-            mConfiguration.set(new UserConfiguration(userId, newConfig));
-
-            if (!newConfig.equals(oldConfig)) {
-                // Simulate what happens when the auto detection configuration is changed.
-                mStrategy.handleAutoTimeZoneConfigChanged();
+        public ConfigurationInternal getConfigurationInternal(int userId) {
+            ConfigurationInternal configuration = mConfigurationInternal.getLatest();
+            if (userId != configuration.getUserId()) {
+                fail("FakeCallback does not support multiple users.");
             }
+            return configuration;
         }
 
         @Override
-        public boolean isAutoDetectionEnabled() {
-            return mConfiguration.getLatest().configuration.isAutoDetectionEnabled();
-        }
-
-        @Override
-        public boolean isGeoDetectionEnabled() {
-            return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+        public int getCurrentUserId() {
+            return mConfigurationInternal.getLatest().getUserId();
         }
 
         @Override
@@ -1149,9 +957,25 @@
             mTimeZoneId.set(zoneId);
         }
 
+        @Override
+        public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+            ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
+            if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+                fail("FakeCallback does not support multiple users");
+            }
+
+            ConfigurationInternal mergedConfiguration = oldConfiguration.merge(newConfiguration);
+            if (!mergedConfiguration.equals(oldConfiguration)) {
+                mConfigurationInternal.set(mergedConfiguration);
+
+                // Note: Unlike the real callback impl, the listener is invoked synchronously.
+                mConfigChangeListener.onChange();
+            }
+        }
+
         void assertKnownUser(int userId) {
-            assertEquals(userId, mCapabilities.getUserId());
-            assertEquals(userId, mConfiguration.getLatest().userId);
+            assertEquals("FakeCallback does not support multiple users",
+                    mConfigurationInternal.getLatest().getUserId(), userId);
         }
 
         void assertTimeZoneNotChanged() {
@@ -1166,43 +990,7 @@
 
         void commitAllChanges() {
             mTimeZoneId.commitLatest();
-            mConfiguration.commitLatest();
-        }
-    }
-
-    private static final class UserConfiguration {
-        public final @UserIdInt int userId;
-        public final TimeZoneConfiguration configuration;
-
-        UserConfiguration(int userId, TimeZoneConfiguration configuration) {
-            this.userId = userId;
-            this.configuration = configuration;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            UserConfiguration that = (UserConfiguration) o;
-            return userId == that.userId
-                    && Objects.equals(configuration, that.configuration);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(userId, configuration);
-        }
-
-        @Override
-        public String toString() {
-            return "UserConfiguration{"
-                    + "userId=" + userId
-                    + ", configuration=" + configuration
-                    + '}';
+            mConfigurationInternal.commitLatest();
         }
     }
 
@@ -1255,64 +1043,14 @@
         }
     }
 
-    /** Simulated user test cases. */
-    enum UserCase {
-        /** A catch-all for users that can set auto time zone config. */
-        UNRESTRICTED,
-        /** A catch-all for users that can't set auto time zone config. */
-        RESTRICTED,
-        /**
-         * Like {@link #UNRESTRICTED}, but auto tz detection is not
-         * supported on the device.
-         */
-        AUTO_DETECT_NOT_SUPPORTED,
-    }
-
-    /**
-     * Creates a {@link TimeZoneCapabilities} object for a user in the specific role with the
-     * supplied configuration.
-     */
-    private static TimeZoneCapabilities createCapabilities(
-            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(userCase + " not recognized");
-        }
-    }
-
     /**
      * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
      * logic.
      */
     private class Script {
 
-        Script initializeUser(
-                @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
-            TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
-            mFakeCallback.initializeUser(userId, capabilities, configuration);
+        Script initializeConfig(ConfigurationInternal configuration) {
+            mFakeCallback.initializeConfig(configuration);
             return this;
         }
 
@@ -1326,10 +1064,9 @@
          * the return value.
          */
         Script simulateUpdateConfiguration(
-                @UserIdInt int userId, TimeZoneConfiguration configuration,
-                boolean expectedResult) {
+                TimeZoneConfiguration configuration, boolean expectedResult) {
             assertEquals(expectedResult,
-                    mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
+                    mTimeZoneDetectorStrategy.updateConfiguration(configuration));
             return this;
         }
 
@@ -1392,16 +1129,14 @@
         /**
          * Verifies that the configuration has been changed to the expected value.
          */
-        Script verifyConfigurationChangedAndReset(
-                @UserIdInt int userId, TimeZoneConfiguration expected) {
-            mFakeCallback.mConfiguration.assertHasBeenSet();
-            UserConfiguration expectedUserConfig = new UserConfiguration(userId, expected);
-            assertEquals(expectedUserConfig, mFakeCallback.mConfiguration.getLatest());
+        Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
+            mFakeCallback.mConfigurationInternal.assertHasBeenSet();
+            assertEquals(expected, mFakeCallback.mConfigurationInternal.getLatest());
             mFakeCallback.commitAllChanges();
 
             // Also confirm the listener triggered.
-            mMockStrategyListener.verifyOnConfigurationChangedCalled();
-            mMockStrategyListener.reset();
+            mMockConfigChangeListener.verifyOnChangeCalled();
+            mMockConfigChangeListener.reset();
             return this;
         }
 
@@ -1410,10 +1145,10 @@
          * {@link TimeZoneConfiguration} have been changed.
          */
         Script verifyConfigurationNotChanged() {
-            mFakeCallback.mConfiguration.assertHasNotBeenSet();
+            mFakeCallback.mConfigurationInternal.assertHasNotBeenSet();
 
             // Also confirm the listener did not trigger.
-            mMockStrategyListener.verifyOnConfigurationChangedNotCalled();
+            mMockConfigChangeListener.verifyOnChangeNotCalled();
             return this;
         }
 
@@ -1448,24 +1183,24 @@
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
 
-    private static class MockStrategyListener implements TimeZoneDetectorStrategy.StrategyListener {
-        private boolean mOnConfigurationChangedCalled;
+    private static class MockConfigChangeListener implements ConfigurationChangeListener {
+        private boolean mOnChangeCalled;
 
         @Override
-        public void onConfigurationChanged() {
-            mOnConfigurationChangedCalled = true;
+        public void onChange() {
+            mOnChangeCalled = true;
         }
 
-        void verifyOnConfigurationChangedCalled() {
-            assertTrue(mOnConfigurationChangedCalled);
+        void verifyOnChangeCalled() {
+            assertTrue(mOnChangeCalled);
         }
 
-        void verifyOnConfigurationChangedNotCalled() {
-            assertFalse(mOnConfigurationChangedCalled);
+        void verifyOnChangeNotCalled() {
+            assertFalse(mOnChangeCalled);
         }
 
         void reset() {
-            mOnConfigurationChangedCalled = false;
+            mOnChangeCalled = false;
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
index d5aee5d..2c719ff 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
@@ -120,17 +120,17 @@
         LocalServices.addService(PackageManagerInternal.class, mPmInternal);
 
         for (int userId : new int[] { USER_PRIMARY, USER_SECONDARY }) {
-            when(mPmInternal.getPackageUidInternal(eq(PKG_SOCIAL), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_SOCIAL), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_SOCIAL));
-            when(mPmInternal.getPackageUidInternal(eq(PKG_CAMERA), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_CAMERA), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_CAMERA));
-            when(mPmInternal.getPackageUidInternal(eq(PKG_PRIVATE), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_PRIVATE), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_PRIVATE));
-            when(mPmInternal.getPackageUidInternal(eq(PKG_PUBLIC), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_PUBLIC), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_PUBLIC));
-            when(mPmInternal.getPackageUidInternal(eq(PKG_FORCE), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_FORCE), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_FORCE));
-            when(mPmInternal.getPackageUidInternal(eq(PKG_COMPLEX), anyInt(), eq(userId)))
+            when(mPmInternal.getPackageUid(eq(PKG_COMPLEX), anyInt(), eq(userId)))
                     .thenReturn(UserHandle.getUid(userId, UID_COMPLEX));
 
             when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyInt(), eq(userId)))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 99433a6..d7e431f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -978,6 +978,7 @@
 
         assertFalse(services.isSameUser(service, 0));
         assertTrue(services.isSameUser(service, 10));
+        assertTrue(services.isSameUser(service, UserHandle.USER_ALL));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index ab4dc47..5796e84 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -103,7 +103,7 @@
             when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
         }
         when(mUm.getUsers()).thenReturn(users);
-        when(mUm.getUsers(anyBoolean())).thenReturn(users);
+        when(mUm.getAliveUsers()).thenReturn(users);
         IntArray profileIds = new IntArray();
         profileIds.add(0);
         profileIds.add(11);
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 9319bea..8644719 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5058,7 +5058,7 @@
                 10, 10, r.getKey(), actionIndex, action, notificationVisibility,
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
-                eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(action), eq(generatedByAssistant));
 
         assertEquals(1, mNotificationRecordLogger.numCalls());
         assertEquals(
@@ -5082,7 +5082,7 @@
                 10, 10, r.getKey(), actionIndex, action, notificationVisibility,
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
-                eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(action), eq(generatedByAssistant));
 
         assertEquals(1, mNotificationRecordLogger.numCalls());
         assertEquals(
@@ -6948,4 +6948,63 @@
         assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
                 mService.getNotificationRecordCount());
     }
+
+    @Test
+    public void testIsVisibleToListener_notEnabled() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(false);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertFalse(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_noAssistant() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(null);
+
+        assertTrue(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_assistant_differentUser() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 0;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertFalse(mService.isVisibleToListener(sbn, info));
+    }
+
+    @Test
+    public void testIsVisibleToListener_assistant_sameUser() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getUserId()).thenReturn(10);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
+        info.userid = 10;
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        when(assistant.isSameUser(anyInt())).thenReturn(true);
+        when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
+        when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
+
+        assertTrue(mService.isVisibleToListener(sbn, info));
+    }
+
 }
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 46c3e22..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;
@@ -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(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);
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 f860e17..59f0a79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -544,7 +544,7 @@
         mActivity = new ActivityBuilder(mAtm)
                 .setTask(mTask)
                 .setLaunchTaskBehind(true)
-                .setConfigChanges(CONFIG_ORIENTATION)
+                .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
 
@@ -1190,7 +1190,7 @@
 
         assertEquals(DESTROYING, mActivity.getState());
         assertTrue(mActivity.finishing);
-        verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+        verify(mActivity).destroyImmediately(anyString());
     }
 
     /**
@@ -1214,7 +1214,7 @@
 
         // Verify that the activity was not actually destroyed, but waits for next one to come up
         // instead.
-        verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+        verify(mActivity, never()).destroyImmediately(anyString());
         assertEquals(FINISHING, mActivity.getState());
         assertTrue(mActivity.mStackSupervisor.mFinishingActivities.contains(mActivity));
     }
@@ -1238,7 +1238,7 @@
         mActivity.completeFinishing("test");
 
         // Verify that the activity is not destroyed immediately, but waits for next one to come up.
-        verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+        verify(mActivity, never()).destroyImmediately(anyString());
         assertEquals(FINISHING, mActivity.getState());
         assertTrue(mActivity.mStackSupervisor.mFinishingActivities.contains(mActivity));
     }
@@ -1250,26 +1250,26 @@
     @Test
     public void testDestroyImmediately_hadApp_finishing() {
         mActivity.finishing = true;
-        mActivity.destroyImmediately(false /* removeFromApp */, "test");
+        mActivity.destroyImmediately("test");
 
         assertEquals(DESTROYING, mActivity.getState());
     }
 
     /**
      * Test that the activity will be moved to destroyed state immediately if it was not marked as
-     * finishing before {@link ActivityRecord#destroyImmediately(boolean, String)}.
+     * finishing before {@link ActivityRecord#destroyImmediately(String)}.
      */
     @Test
     public void testDestroyImmediately_hadApp_notFinishing() {
         mActivity.finishing = false;
-        mActivity.destroyImmediately(false /* removeFromApp */, "test");
+        mActivity.destroyImmediately("test");
 
         assertEquals(DESTROYED, mActivity.getState());
     }
 
     /**
      * Test that an activity with no process attached and that is marked as finishing will be
-     * removed from task when {@link ActivityRecord#destroyImmediately(boolean, String)} is called.
+     * removed from task when {@link ActivityRecord#destroyImmediately(String)} is called.
      */
     @Test
     public void testDestroyImmediately_noApp_finishing() {
@@ -1277,7 +1277,7 @@
         mActivity.finishing = true;
         final Task task = mActivity.getTask();
 
-        mActivity.destroyImmediately(false /* removeFromApp */, "test");
+        mActivity.destroyImmediately("test");
 
         assertEquals(DESTROYED, mActivity.getState());
         assertNull(mActivity.getTask());
@@ -1294,7 +1294,7 @@
         mActivity.finishing = false;
         final Task task = mActivity.getTask();
 
-        mActivity.destroyImmediately(false /* removeFromApp */, "test");
+        mActivity.destroyImmediately("test");
 
         assertEquals(DESTROYED, mActivity.getState());
         assertEquals(task, mActivity.getTask());
@@ -1310,7 +1310,7 @@
 
         mActivity.safelyDestroy("test");
 
-        verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+        verify(mActivity, never()).destroyImmediately(anyString());
     }
 
     /**
@@ -1322,7 +1322,7 @@
 
         mActivity.safelyDestroy("test");
 
-        verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString());
+        verify(mActivity).destroyImmediately(anyString());
     }
 
     @Test
@@ -1655,13 +1655,13 @@
         assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
 
-        secondActivity.destroyImmediately(true, "");
+        secondActivity.destroyImmediately("");
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
                 .diff(wpc.getRequestedOverrideConfiguration()));
 
-        firstActivity.destroyImmediately(true, "");
+        firstActivity.destroyImmediately("");
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
@@ -1714,6 +1714,27 @@
 
     }
 
+    @Test
+    public void testProcessInfoUpdateWhenSetState() {
+        spyOn(mActivity.app);
+        verifyProcessInfoUpdate(RESUMED, true /* shouldUpdate */, true /* activityChange */);
+        verifyProcessInfoUpdate(PAUSED, false /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(STOPPED, false /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(STARTED, true /* shouldUpdate */, true /* activityChange */);
+
+        mActivity.app.removeActivity(mActivity, true /* keepAssociation */);
+        verifyProcessInfoUpdate(DESTROYING, true /* shouldUpdate */, false /* activityChange */);
+        verifyProcessInfoUpdate(DESTROYED, true /* shouldUpdate */, false /* activityChange */);
+    }
+
+    private void verifyProcessInfoUpdate(ActivityState state, boolean shouldUpdate,
+            boolean activityChange) {
+        reset(mActivity.app);
+        mActivity.setState(state, "test");
+        verify(mActivity.app, times(shouldUpdate ? 1 : 0)).updateProcessInfo(anyBoolean(),
+                eq(activityChange), anyBoolean(), anyBoolean());
+    }
+
     /**
      * Creates an activity on display. For non-default display request it will also create a new
      * display with custom DisplayInfo.
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 1e95aba..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;
@@ -38,13 +39,8 @@
 
 import android.app.WaitResult;
 import android.content.pm.ActivityInfo;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
-import android.view.DisplayInfo;
 
 import androidx.test.filters.MediumTest;
 
@@ -180,53 +176,28 @@
                 eq(true) /* focused */);
     }
 
+    /**
+     * Ensures that a trusted display can launch arbitrary activity and an untrusted display can't.
+     */
     @Test
-    /** Ensures that a trusted virtual display can launch arbitrary activities. */
-    public void testTrustedVirtualDisplayCanLaunchActivities() {
-        final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final Task stack = new StackBuilder(mRootWindowContainer)
-                .setDisplay(newDisplay).build();
-        final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
-        VirtualDisplay virtualDisplay = createVirtualDisplay(true);
-        final boolean allowed = mSupervisor.isCallerAllowedToLaunchOnDisplay(1234, 1234,
-                virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
+    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);
 
-        assertThat(allowed).isTrue();
+        doReturn(true).when(display).isTrusted();
+        final boolean allowedOnTrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
 
-        virtualDisplay.release();
-    }
+        assertThat(allowedOnTrusted).isTrue();
 
-    @Test
-    /** Ensures that an untrusted virtual display cannot launch arbitrary activities. */
-    public void testUntrustedVirtualDisplayCannotLaunchActivities() {
-        final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        final Task stack = new StackBuilder(mRootWindowContainer)
-                .setDisplay(newDisplay).build();
-        final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
-        VirtualDisplay virtualDisplay = createVirtualDisplay(false);
-        final boolean allowed = mSupervisor.isCallerAllowedToLaunchOnDisplay(1234, 1234,
-                virtualDisplay.getDisplay().getDisplayId(), unresizableActivity.info);
+        doReturn(false).when(display).isTrusted();
+        final boolean allowedOnUntrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
 
-        assertThat(allowed).isFalse();
-
-        virtualDisplay.release();
-    }
-
-    private VirtualDisplay createVirtualDisplay(boolean trusted) {
-        final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-        final DisplayInfo displayInfo = new DisplayInfo();
-        final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
-        defaultDisplay.getDisplayInfo(displayInfo);
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-        if (trusted) {
-            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
-        }
-
-        final ImageReader imageReader = ImageReader.newInstance(
-                displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
-
-        return dm.createVirtualDisplay("virtualDisplay", displayInfo.logicalWidth,
-                displayInfo.logicalHeight,
-                displayInfo.logicalDensityDpi, imageReader.getSurface(), flags);
+        assertThat(allowedOnUntrusted).isFalse();
     }
 }
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 d54b4a0..f8baf84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1110,7 +1110,7 @@
         performLayout(mDisplayContent);
 
         // The frame is empty because the requested height is zero.
-        assertTrue(win.getFrameLw().isEmpty());
+        assertTrue(win.getFrame().isEmpty());
         // The window should be scheduled to resize then the client may report a new non-empty size.
         win.updateResizingWindowIfNeeded();
         assertThat(mWm.mResizingWindows).contains(win);
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 0675c6d..2d834ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -114,7 +114,7 @@
         mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
         // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
         // changing those frames.
-        doNothing().when(mWindow).computeFrameLw();
+        doNothing().when(mWindow).computeFrame();
 
         final WindowManager.LayoutParams attrs = mWindow.mAttrs;
         attrs.width = MATCH_PARENT;
@@ -179,7 +179,7 @@
 
         WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
         win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
-        win.getFrameLw().set(0, 0, 500, 100);
+        win.getFrame().set(0, 0, 500, 100);
 
         addWindow(win);
         InsetsStateController controller = mDisplayContent.getInsetsStateController();
@@ -207,7 +207,7 @@
         mDisplayPolicy.removeWindowLw(mStatusBarWindow);  // Removes the existing one.
         WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
         win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
-        win.getFrameLw().set(0, 0, 500, 100);
+        win.getFrame().set(0, 0, 500, 100);
 
         addWindow(win);
         mDisplayContent.getInsetsStateController().onPostLayout();
@@ -232,7 +232,7 @@
         WindowState win1 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel1");
         win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
         win1.mAttrs.gravity = Gravity.TOP;
-        win1.getFrameLw().set(0, 0, 200, 500);
+        win1.getFrame().set(0, 0, 200, 500);
         addWindow(win1);
 
         assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_TOP);
@@ -241,7 +241,7 @@
         WindowState win2 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel2");
         win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
         win2.mAttrs.gravity = Gravity.BOTTOM;
-        win2.getFrameLw().set(0, 0, 200, 500);
+        win2.getFrame().set(0, 0, 200, 500);
         addWindow(win2);
 
         assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_BOTTOM);
@@ -250,7 +250,7 @@
         WindowState win3 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel3");
         win3.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
         win3.mAttrs.gravity = Gravity.LEFT;
-        win3.getFrameLw().set(0, 0, 200, 500);
+        win3.getFrame().set(0, 0, 200, 500);
         addWindow(win3);
 
         assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_LEFT);
@@ -259,7 +259,7 @@
         WindowState win4 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel4");
         win4.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
         win4.mAttrs.gravity = Gravity.RIGHT;
-        win4.getFrameLw().set(0, 0, 200, 500);
+        win4.getFrame().set(0, 0, 200, 500);
         addWindow(win4);
 
         assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_RIGHT);
@@ -274,11 +274,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, 0);
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -290,11 +290,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), 0, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -306,11 +306,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -322,11 +322,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, 0);
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -342,11 +342,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -362,11 +362,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), 0, 0);
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -381,11 +381,11 @@
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), DISPLAY_CUTOUT_HEIGHT, 0);
         assertInsetByTopBottom(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
     }
 
@@ -402,10 +402,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), 0, 0);
     }
 
     @Test
@@ -422,10 +422,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, 0);
     }
 
     @Test
@@ -442,10 +442,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getContentFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), 0, 0, 0, 0);
     }
 
     @Test
@@ -462,10 +462,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getContentFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), 0, 0, 0, 0);
     }
 
     @Test
@@ -484,10 +484,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), 0, 0);
     }
 
     @Test
@@ -506,10 +506,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, 0);
     }
 
     @Test
@@ -529,10 +529,10 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), 0, 0);
     }
 
 
@@ -549,11 +549,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
-        assertInsetBy(mWindow.getContentFrameLw(),
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mWindow.getContentFrame(),
                 DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
     }
 
     @Test
@@ -570,11 +570,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
-        assertInsetBy(mWindow.getContentFrameLw(),
+        assertInsetBy(mWindow.getStableFrame(), NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
+        assertInsetBy(mWindow.getContentFrame(),
                 NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
     }
 
     @Test
@@ -594,8 +594,8 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
-        assertInsetBy(mWindow.getContentFrameLw(),
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mWindow.getContentFrame(),
                 DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
     }
@@ -615,7 +615,7 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getDisplayFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
     }
 
     @Test
@@ -636,8 +636,8 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
-        assertInsetBy(mWindow.getContentFrameLw(),
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
+        assertInsetBy(mWindow.getContentFrame(),
                 DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
     }
@@ -655,11 +655,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getContentFrame(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
     }
 
     @Test
@@ -676,12 +676,12 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getContentFrame(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
     }
 
     @Test
@@ -698,11 +698,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getContentFrame(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
     }
 
     @Test
@@ -719,11 +719,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getStableFrameLw(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
-        assertInsetBy(mWindow.getContentFrameLw(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getStableFrame(), 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
+        assertInsetBy(mWindow.getContentFrame(), DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), 0, 0, 0, 0);
     }
 
     @Test
@@ -740,11 +740,11 @@
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
         assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getStableFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getVisibleFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+        assertInsetBy(mWindow.getDisplayFrame(), 0, 0, 0, 0);
     }
 
     @Test
@@ -904,34 +904,34 @@
         updateDecorWindow(decorWindow, MATCH_PARENT, DECOR_WINDOW_INSET, TOP);
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), DECOR_WINDOW_INSET, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), DECOR_WINDOW_INSET, NAV_BAR_HEIGHT);
 
         // Decor on bottom
         updateDecorWindow(decorWindow, MATCH_PARENT, DECOR_WINDOW_INSET, BOTTOM);
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT,
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT,
                 DECOR_WINDOW_INSET);
 
         // Decor on the left
         updateDecorWindow(decorWindow, DECOR_WINDOW_INSET, MATCH_PARENT, LEFT);
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        assertInsetBy(mWindow.getContentFrameLw(), DECOR_WINDOW_INSET, STATUS_BAR_HEIGHT, 0,
+        assertInsetBy(mWindow.getContentFrame(), DECOR_WINDOW_INSET, STATUS_BAR_HEIGHT, 0,
                 NAV_BAR_HEIGHT);
 
         // Decor on the right
         updateDecorWindow(decorWindow, DECOR_WINDOW_INSET, MATCH_PARENT, RIGHT);
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        assertInsetBy(mWindow.getContentFrameLw(), 0, STATUS_BAR_HEIGHT, DECOR_WINDOW_INSET,
+        assertInsetBy(mWindow.getContentFrame(), 0, STATUS_BAR_HEIGHT, DECOR_WINDOW_INSET,
                 NAV_BAR_HEIGHT);
 
         // Decor not allowed as inset
         updateDecorWindow(decorWindow, DECOR_WINDOW_INSET, DECOR_WINDOW_INSET, TOP);
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getContentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
     }
 
     private void updateDecorWindow(WindowState decorWindow, int width, int height, int gravity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 4483f8c..b50530e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -297,7 +297,7 @@
 
         final WindowState navigationBar = createNavigationBarWindow();
 
-        navigationBar.getFrameLw().set(new Rect(100, 200, 200, 300));
+        navigationBar.getFrame().set(new Rect(100, 200, 200, 300));
 
         assertFalse("Freeform is overlapping with navigation bar",
                 DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar));
@@ -377,7 +377,7 @@
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
-        mImeWindow.getGivenContentInsetsLw().set(0, displayInfo.logicalHeight, 0, 0);
+        mImeWindow.mGivenContentInsets.set(0, displayInfo.logicalHeight, 0, 0);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
 
         displayPolicy.beginLayoutLw(mDisplayContent.mDisplayFrames, 0 /* UI mode */);
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/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 1d6dd0b..a0fa936 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -61,7 +61,7 @@
     @Test
     public void testPostLayout() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindow(statusBar, null, null);
         mProvider.onPostLayout();
@@ -76,9 +76,9 @@
     @Test
     public void testPostLayout_givenInsets() {
         final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
-        ime.getFrameLw().set(0, 0, 500, 100);
-        ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
-        ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+        ime.getFrame().set(0, 0, 500, 100);
+        ime.mGivenContentInsets.set(0, 0, 0, 60);
+        ime.mGivenVisibleInsets.set(0, 0, 0, 75);
         ime.mHasSurface = true;
         mProvider.setWindow(ime, null, null);
         mProvider.onPostLayout();
@@ -94,7 +94,7 @@
     @Test
     public void testPostLayout_invisible() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
         mProvider.onPostLayout();
         assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
@@ -104,7 +104,7 @@
     @Test
     public void testPostLayout_frameProvider() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindow(statusBar,
                 (displayFrames, windowState, rect) -> {
@@ -118,7 +118,7 @@
     public void testUpdateControlForTarget() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
 
         // We must not have control or control target before we have the insets source window.
         mProvider.updateControlForTarget(target, true /* force */);
@@ -163,7 +163,7 @@
     public void testUpdateControlForFakeTarget() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
         mProvider.updateControlForFakeTarget(target);
         assertNotNull(mProvider.getControl(target));
@@ -176,7 +176,7 @@
     public void testUpdateSourceFrameForIme() {
         final WindowState inputMethod = createWindow(null, TYPE_INPUT_METHOD, "inputMethod");
 
-        inputMethod.getFrameLw().set(new Rect(0, 400, 500, 500));
+        inputMethod.getFrame().set(new Rect(0, 400, 500, 500));
 
         mImeProvider.setWindow(inputMethod, null, null);
         mImeProvider.setServerVisible(false);
@@ -190,7 +190,7 @@
         mImeProvider.setServerVisible(true);
         mImeSource.setVisible(true);
         mImeProvider.updateSourceFrame();
-        assertEquals(inputMethod.getFrameLw(), mImeSource.getFrame());
+        assertEquals(inputMethod.getFrame(), mImeSource.getFrame());
         insets = mImeSource.calculateInsets(new Rect(0, 0, 500, 500),
                 false /* ignoreVisibility */);
         assertEquals(Insets.of(0, 0, 0, 100), insets);
@@ -200,7 +200,7 @@
     public void testInsetsModified() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
         mProvider.updateControlForTarget(target, false /* force */);
         InsetsState state = new InsetsState();
@@ -213,7 +213,7 @@
     public void testInsetsModified_noControl() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
         InsetsState state = new InsetsState();
         state.getSource(ITYPE_STATUS_BAR).setVisible(false);
@@ -224,7 +224,7 @@
     @Test
     public void testInsetGeometries() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        statusBar.getFrameLw().set(0, 0, 500, 100);
+        statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindow(statusBar, null, null);
         mProvider.onPostLayout();
@@ -236,7 +236,7 @@
                         false /* ignoreVisibility */));
 
         // Don't apply left insets if window is left-of inset-window but still overlaps
-        statusBar.getFrameLw().set(100, 0, 0, 0);
+        statusBar.getFrame().set(100, 0, 0, 0);
         assertEquals(Insets.of(0, 0, 0, 0),
                 mProvider.getSource().calculateInsets(new Rect(-100, 0, 400, 500),
                         false /* ignoreVisibility */));
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 085230d..59f8cc8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -346,8 +346,7 @@
         assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
 
         provider.getSource().setVisible(false);
-        mDisplayContent.getInsetsPolicy().showTransient(
-                IntArray.wrap(new int[] { ITYPE_STATUS_BAR }));
+        mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR });
 
         assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
         assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
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 1ec9bd2..b89d168 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -890,8 +890,8 @@
 
         // 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);
     }
 
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 72899e7..191c33c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -184,7 +184,7 @@
         };
         activities[0].detachFromProcess();
         activities[1].finishing = true;
-        activities[1].destroyImmediately(true /* removeFromApp */, "test");
+        activities[1].destroyImmediately("test");
         spyOn(wpc);
         doReturn(true).when(wpc).isRemoved();
 
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 fc54e1d..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;
@@ -245,17 +246,17 @@
         final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
         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(mAtm).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,6 +305,33 @@
     }
 
     @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(
@@ -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());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index bce1142..ca3f815 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -53,7 +53,6 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.internal.annotations.GuardedBy;
 
 import org.junit.After;
 import org.junit.Before;
@@ -76,14 +75,10 @@
 
     private static final int WAIT_TIMEOUT_MS = 5000;
     private static final Object sLock = new Object();
-    @GuardedBy("sLock")
-    private static boolean sTaskStackChangedCalled;
-    private static boolean sActivityBResumed;
 
     @Before
     public void setUp() throws Exception {
         mService = ActivityManager.getService();
-        sTaskStackChangedCalled = false;
     }
 
     @After
@@ -94,47 +89,33 @@
 
     @Test
     @Presubmit
-    @FlakyTest(bugId = 130388819)
     public void testTaskStackChanged_afterFinish() throws Exception {
+        final TestActivity activity = startTestActivity(ActivityA.class);
+        final CountDownLatch latch = new CountDownLatch(1);
         registerTaskStackChangedListener(new TaskStackListener() {
             @Override
             public void onTaskStackChanged() throws RemoteException {
-                synchronized (sLock) {
-                    sTaskStackChangedCalled = true;
-                }
+                latch.countDown();
             }
         });
 
-        Context context = getInstrumentation().getContext();
-        context.startActivity(
-                new Intent(context, ActivityA.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        UiDevice.getInstance(getInstrumentation()).waitForIdle();
-        synchronized (sLock) {
-            assertTrue(sTaskStackChangedCalled);
-        }
-        assertTrue(sActivityBResumed);
+        activity.finish();
+        waitForCallback(latch);
     }
 
     @Test
     @Presubmit
     public void testTaskStackChanged_resumeWhilePausing() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
         registerTaskStackChangedListener(new TaskStackListener() {
             @Override
             public void onTaskStackChanged() throws RemoteException {
-                synchronized (sLock) {
-                    sTaskStackChangedCalled = true;
-                }
+                latch.countDown();
             }
         });
 
-        final Context context = getInstrumentation().getContext();
-        context.startActivity(new Intent(context, ResumeWhilePausingActivity.class).addFlags(
-                Intent.FLAG_ACTIVITY_NEW_TASK));
-        UiDevice.getInstance(getInstrumentation()).waitForIdle();
-
-        synchronized (sLock) {
-            assertTrue(sTaskStackChangedCalled);
-        }
+        startTestActivity(ResumeWhilePausingActivity.class);
+        waitForCallback(latch);
     }
 
     @Test
@@ -512,7 +493,7 @@
         try {
             final boolean result = latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
             if (!result) {
-                throw new RuntimeException("Timed out waiting for task stack change notification");
+                throw new AssertionError("Timed out waiting for task stack change notification");
             }
         } catch (InterruptedException e) {
         }
@@ -569,19 +550,6 @@
     }
 
     public static class ActivityA extends TestActivity {
-
-        private boolean mActivityBLaunched = false;
-
-        @Override
-        protected void onPostResume() {
-            super.onPostResume();
-            if (mActivityBLaunched) {
-                return;
-            }
-            mActivityBLaunched = true;
-            finish();
-            startActivity(new Intent(this, ActivityB.class));
-        }
     }
 
     public static class ActivityB extends TestActivity {
@@ -589,10 +557,6 @@
         @Override
         protected void onPostResume() {
             super.onPostResume();
-            synchronized (sLock) {
-                sTaskStackChangedCalled = false;
-            }
-            sActivityBResumed = true;
             finish();
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 4a8e8da..dc85904 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -86,11 +86,6 @@
     }
 
     @Override
-    public int getMaxWallpaperLayer() {
-        return 0;
-    }
-
-    @Override
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
         return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
@@ -377,11 +372,6 @@
     }
 
     @Override
-    public boolean isTopLevelWindow(int windowType) {
-        return false;
-    }
-
-    @Override
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
     }
 
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 6ed7622..78dfd40 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -55,6 +55,18 @@
     }
 
     @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 = createTestActivityRecord(mDisplayContent);
         final ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
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 ed9e270..63367ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -133,8 +133,8 @@
         int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
 
         // Check that the wallpaper is correctly scaled
-        assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrameLw());
-        Rect portraitFrame = wallpaperWindow.getFrameLw();
+        assertEquals(new Rect(0, 0, expectedWidth, displayHeight), wallpaperWindow.getFrame());
+        Rect portraitFrame = wallpaperWindow.getFrame();
 
         // Rotate the display
         dc.getDisplayRotation().updateOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, true);
@@ -149,7 +149,7 @@
 
         // Check that the wallpaper has the same frame in landscape than in portrait
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getConfiguration().orientation);
-        assertEquals(portraitFrame, wallpaperWindow.getFrameLw());
+        assertEquals(portraitFrame, wallpaperWindow.getFrame());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index eb2aa41..ca3626d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -89,29 +89,29 @@
     }
 
     private void assertFrame(WindowState w, Rect frame) {
-        assertEquals(w.getFrameLw(), frame);
+        assertEquals(w.getFrame(), frame);
     }
 
     private void assertFrame(WindowState w, int left, int top, int right, int bottom) {
-        assertRect(w.getFrameLw(), left, top, right, bottom);
+        assertRect(w.getFrame(), left, top, right, bottom);
     }
 
     private void assertRelFrame(WindowState w, int left, int top, int right, int bottom) {
-        assertRect(w.getRelativeFrameLw(), left, top, right, bottom);
+        assertRect(w.getRelativeFrame(), left, top, right, bottom);
     }
 
     private void assertContentFrame(WindowState w, Rect expectedRect) {
-        assertRect(w.getContentFrameLw(), expectedRect.left, expectedRect.top, expectedRect.right,
+        assertRect(w.getContentFrame(), expectedRect.left, expectedRect.top, expectedRect.right,
                 expectedRect.bottom);
     }
 
     private void assertVisibleFrame(WindowState w, Rect expectedRect) {
-        assertRect(w.getVisibleFrameLw(), expectedRect.left, expectedRect.top, expectedRect.right,
+        assertRect(w.getVisibleFrame(), expectedRect.left, expectedRect.top, expectedRect.right,
                 expectedRect.bottom);
     }
 
     private void assertStableFrame(WindowState w, Rect expectedRect) {
-        assertRect(w.getStableFrameLw(), expectedRect.left, expectedRect.top, expectedRect.right,
+        assertRect(w.getStableFrame(), expectedRect.left, expectedRect.top, expectedRect.right,
                 expectedRect.bottom);
     }
 
@@ -155,7 +155,7 @@
         // the difference between mFrame and ContentFrame. Visible
         // and stable frames work the same way.
         w.getWindowFrames().setFrames(pf, df, cf, vf, dcf, sf);
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 0, 0, 1000, 1000);
         assertRelFrame(w, 0, 0, 1000, 1000);
         assertContentInset(w, 0, topContentInset, 0, bottomContentInset);
@@ -170,14 +170,14 @@
         w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT
         w.mRequestedWidth = 100;
         w.mRequestedHeight = 100;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 100, 100, 200, 200);
         assertRelFrame(w, 100, 100, 200, 200);
         assertContentInset(w, 0, 0, 0, 0);
         // In this case the frames are shrunk to the window frame.
-        assertContentFrame(w, w.getFrameLw());
-        assertVisibleFrame(w, w.getFrameLw());
-        assertStableFrame(w, w.getFrameLw());
+        assertContentFrame(w, w.getFrame());
+        assertVisibleFrame(w, w.getFrame());
+        assertStableFrame(w, w.getFrame());
     }
 
     @Test
@@ -193,7 +193,7 @@
         // Here the window has FILL_PARENT, FILL_PARENT
         // so we expect it to fill the entire available frame.
         w.getWindowFrames().setFrames(pf, pf, pf, pf, pf, pf);
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 0, 0, 1000, 1000);
         assertRelFrame(w, 0, 0, 1000, 1000);
 
@@ -202,14 +202,14 @@
         // and we use mRequestedWidth/mRequestedHeight
         w.mAttrs.width = 300;
         w.mAttrs.height = 300;
-        w.computeFrameLw();
+        w.computeFrame();
         // Explicit width and height without requested width/height
         // gets us nothing.
         assertFrame(w, 0, 0, 0, 0);
 
         w.mRequestedWidth = 300;
         w.mRequestedHeight = 300;
-        w.computeFrameLw();
+        w.computeFrame();
         // With requestedWidth/Height we can freely choose our size within the
         // parent bounds.
         assertFrame(w, 0, 0, 300, 300);
@@ -222,14 +222,14 @@
         w.mRequestedWidth = -1;
         w.mAttrs.width = 100;
         w.mAttrs.height = 100;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 0, 0, 100, 100);
         w.mAttrs.flags = 0;
 
         // But sizes too large will be clipped to the containing frame
         w.mRequestedWidth = 1200;
         w.mRequestedHeight = 1200;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 0, 0, 1000, 1000);
 
         // Before they are clipped though windows will be shifted
@@ -237,7 +237,7 @@
         w.mAttrs.y = 300;
         w.mRequestedWidth = 1000;
         w.mRequestedHeight = 1000;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 0, 0, 1000, 1000);
 
         // If there is room to move around in the parent frame the window will be shifted according
@@ -247,18 +247,18 @@
         w.mRequestedWidth = 300;
         w.mRequestedHeight = 300;
         w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 700, 0, 1000, 300);
         assertRelFrame(w, 700, 0, 1000, 300);
         w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 700, 700, 1000, 1000);
         assertRelFrame(w, 700, 700, 1000, 1000);
         // Window specified  x and y are interpreted as offsets in the opposite
         // direction of gravity
         w.mAttrs.x = 100;
         w.mAttrs.y = 100;
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, 600, 600, 900, 900);
         assertRelFrame(w, 600, 600, 900, 900);
     }
@@ -285,12 +285,12 @@
         final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, pf, pf, pf, pf, pf);
-        w.computeFrameLw();
+        w.computeFrame();
         // For non fullscreen tasks the containing frame is based off the
         // task bounds not the parent frame.
-        assertEquals(resolvedTaskBounds, w.getFrameLw());
-        assertEquals(0, w.getRelativeFrameLw().left);
-        assertEquals(0, w.getRelativeFrameLw().top);
+        assertEquals(resolvedTaskBounds, w.getFrame());
+        assertEquals(0, w.getRelativeFrame().left);
+        assertEquals(0, w.getRelativeFrame().top);
         assertContentFrame(w, resolvedTaskBounds);
         assertContentInset(w, 0, 0, 0, 0);
 
@@ -300,10 +300,10 @@
         final int cfBottom = logicalHeight / 2;
         final Rect cf = new Rect(0, 0, cfRight, cfBottom);
         windowFrames.setFrames(pf, pf, cf, cf, pf, cf);
-        w.computeFrameLw();
-        assertEquals(resolvedTaskBounds, w.getFrameLw());
-        assertEquals(0, w.getRelativeFrameLw().left);
-        assertEquals(0, w.getRelativeFrameLw().top);
+        w.computeFrame();
+        assertEquals(resolvedTaskBounds, w.getFrame());
+        assertEquals(0, w.getRelativeFrame().left);
+        assertEquals(0, w.getRelativeFrame().top);
         int contentInsetRight = resolvedTaskBounds.right - cfRight;
         int contentInsetBottom = resolvedTaskBounds.bottom - cfBottom;
         assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom);
@@ -334,12 +334,12 @@
 
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, df, cf, vf, dcf, sf);
-        w.computeFrameLw();
+        w.computeFrame();
         assertPolicyCrop(w, 0, cf.top, logicalWidth, cf.bottom);
 
         windowFrames.mDecorFrame.setEmpty();
         // Likewise with no decor frame we would get no crop
-        w.computeFrameLw();
+        w.computeFrame();
         assertPolicyCrop(w, 0, 0, logicalWidth, logicalHeight);
 
         // Now we set up a window which doesn't fill the entire decor frame.
@@ -353,7 +353,7 @@
         w.mAttrs.height = logicalHeight / 2;
         w.mRequestedWidth = logicalWidth / 2;
         w.mRequestedHeight = logicalHeight / 2;
-        w.computeFrameLw();
+        w.computeFrame();
 
         // Normally the crop is shrunk from the decor frame
         // to the computed window frame.
@@ -390,7 +390,7 @@
         final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, pf, pf, pf, pf, pf);
-        w.computeFrameLw();
+        w.computeFrame();
         // For non fullscreen tasks the containing frame is based off the
         // task bounds not the parent frame.
         assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
@@ -408,7 +408,7 @@
         task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         task.setBounds(null);
         windowFrames.setFrames(pf, pf, cf, cf, pf, cf);
-        w.computeFrameLw();
+        w.computeFrame();
         assertFrame(w, cf);
         assertContentFrame(w, cf);
         assertContentInset(w, 0, 0, 0, 0);
@@ -430,7 +430,7 @@
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, pf, pf, pf, pf, pf);
         windowFrames.setDisplayCutout(cutout);
-        w.computeFrameLw();
+        w.computeFrame();
 
         assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetTop(), 50);
         assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetBottom(), 0);
@@ -469,20 +469,20 @@
 
         task.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, df, cf, vf, dcf, sf);
-        w.computeFrameLw();
+        w.computeFrame();
 
         final Rect expected = new Rect(winRect.left, cf.bottom - winRect.height(),
                 winRect.right, cf.bottom);
-        assertEquals(expected, w.getFrameLw());
-        assertEquals(expected, w.getContentFrameLw());
-        assertEquals(expected, w.getVisibleFrameLw());
+        assertEquals(expected, w.getFrame());
+        assertEquals(expected, w.getContentFrame());
+        assertEquals(expected, w.getVisibleFrame());
 
         // Now check that it won't get moved beyond the top and then has appropriate insets
         winRect.bottom = 600;
         task.setBounds(winRect);
         w.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, df, cf, vf, dcf, sf);
-        w.computeFrameLw();
+        w.computeFrame();
 
         assertFrame(w, winRect.left, 0, winRect.right, winRect.height());
         expected.top = 0;
@@ -492,8 +492,8 @@
 
         // Check that it's moved back without ime insets
         w.getWindowFrames().setFrames(pf, df, pf, pf, dcf, sf);
-        w.computeFrameLw();
-        assertEquals(winRect, w.getFrameLw());
+        w.computeFrame();
+        assertEquals(winRect, w.getFrame());
     }
 
     private WindowState createWindow() {
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 9135297..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));
     }
@@ -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,8 +890,7 @@
             }
         }
         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);
@@ -942,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,7 +930,7 @@
         final Task stack = createStack();
         final Task task = createTask(stack);
         final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        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/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f095fd4..9603d28 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -479,7 +479,7 @@
         app.mHasSurface = true;
         app.mSurfaceControl = mock(SurfaceControl.class);
         try {
-            app.getFrameLw().set(10, 20, 60, 80);
+            app.getFrame().set(10, 20, 60, 80);
             app.updateSurfacePosition(t);
 
             app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_90, true);
@@ -519,7 +519,7 @@
                 new Rect(95, 378, 105, 400));
         wf.setDisplayCutout(new WmDisplayCutout(cutout, new Size(200, 400)));
 
-        app.computeFrameLw();
+        app.computeFrame();
         assertThat(app.getWmDisplayCutout().getDisplayCutout(), is(cutout.inset(7, 10, 5, 20)));
     }
 
@@ -633,7 +633,7 @@
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
 
         final DisplayContent dc = createNewDisplay();
-        win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
+        win0.getFrame().offsetTo(PARENT_WINDOW_OFFSET, 0);
         dc.reparentDisplayContent(win0, win0.getSurfaceControl());
         dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
 
@@ -644,7 +644,7 @@
         win1.mHasSurface = true;
         win1.mSurfaceControl = mock(SurfaceControl.class);
         win1.mAttrs.surfaceInsets.set(1, 2, 3, 4);
-        win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
+        win1.getFrame().offsetTo(WINDOW_OFFSET, 0);
         win1.updateSurfacePosition(t);
         win1.getTransformationMatrix(values, matrix);
 
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 5ce61b4..38c4e0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1031,10 +1031,7 @@
         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);
+            mService.mTaskOrganizerController.registerTaskOrganizer(this);
             WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
                     displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
             mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
@@ -1134,7 +1131,7 @@
         }
 
         @Override
-        public boolean isGoneForLayoutLw() {
+        public boolean isGoneForLayout() {
             return false;
         }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1e5d92b..81aad97 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1653,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 0ea84da..26d46db 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1276,7 +1276,8 @@
          * @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();
@@ -1285,21 +1286,15 @@
 
             sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
 
-            try {
-                return (new AudioRecord.Builder())
-                            .setAudioAttributes(attributes)
-                            .setAudioFormat((new AudioFormat.Builder())
-                                .setChannelMask(originalFormat.getChannelMask())
-                                .setEncoding(originalFormat.getEncoding())
-                                .setSampleRate(originalFormat.getSampleRate())
-                                .build())
-                            .setSessionId(event.getCaptureSession())
-                            .build();
-            } catch (IllegalArgumentException | UnsupportedOperationException e) {
-                Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
-                        + "), failed to create AudioRecord");
-                return null;
-            }
+            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
@@ -1325,13 +1320,13 @@
                     // 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
-                            if (capturedData != null) {
+                            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/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/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 09c1659..944edd5 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -687,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();
@@ -722,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);
@@ -760,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";
@@ -1625,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/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/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/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/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 91ec211..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,9 +87,9 @@
                 }
 
                 layersTrace {
-                    navBarLayerIsAlwaysVisible()
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
+                    noUncoveredRegions(rotation, enabled = false)
                     navBarLayerRotatesAndScales(rotation, bugId = 140855415)
                     statusBarLayerRotatesScales(rotation)
 
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/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 4f5a305..7dd003e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -15,6 +15,7 @@
 android_test {
     name: "RollbackTest",
     manifest: "RollbackTest/AndroidManifest.xml",
+    platform_apis: true,
     srcs: ["RollbackTest/src/**/*.java"],
     static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index de51c5c..0db2b2a 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -175,7 +175,7 @@
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
 
             UserManager um = (UserManager) context.getSystemService(context.USER_SERVICE);
-            List<Integer> userIds = um.getUsers(true)
+            List<Integer> userIds = um.getAliveUsers()
                     .stream().map(user -> user.id).collect(Collectors.toList());
             assertThat(InstallUtils.isOnlyInstalledForUser(TestApp.A,
                     context.getUserId(), userIds)).isTrue();
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 76f8df0..f55d4d4 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -26,7 +26,7 @@
 java_test_host {
     name: "StagedInstallInternalTest",
     srcs: ["src/**/*.java"],
-    libs: ["tradefed"],
+    libs: ["tradefed", "cts-shim-host-lib"],
     static_libs: [
         "testng",
         "compatibility-tradefed",
@@ -35,6 +35,7 @@
         "cts-install-lib-host",
     ],
     data: [
+        ":com.android.apex.apkrollback.test_v1",
         ":com.android.apex.cts.shim.v2_prebuilt",
         ":TestAppAv1",
     ],
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 12f1750..e5411de 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -47,11 +47,7 @@
 
 @RunWith(JUnit4.class)
 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");
 
@@ -119,6 +115,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 792e29d..702f871 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -16,6 +16,8 @@
 
 package com.android.tests.stagedinstallinternal.host;
 
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
@@ -41,6 +43,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 {
@@ -79,7 +84,8 @@
             Log.e(TAG, e);
         }
         deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
-                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
+                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
+                "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex");
     }
 
     @Before
@@ -148,43 +154,78 @@
         runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Verify");
     }
 
+    // Test waiting time for staged session to be ready using adb staged install can be altered
     @Test
-    public void testAdbStagedInstallWaitForReadyFlagWorks() throws Exception {
+    public void testAdbStagdReadyTimeoutFlagWorks() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
 
-        File apexFile = mTestUtils.getTestFile(SHIM_V2);
-        String output = getDevice().executeAdbCommand("install", "--staged",
-                "--wait-for-staged-ready", "60000", apexFile.getAbsolutePath());
+        final File apexFile = mTestUtils.getTestFile(SHIM_V2);
+        final String output = getDevice().executeAdbCommand("install", "--staged",
+                "--staged-ready-timeout", "60000", apexFile.getAbsolutePath());
         assertThat(output).contains("Reboot device to apply staged session");
-        String sessionId = getDevice().executeShellCommand(
+        final String sessionId = getDevice().executeShellCommand(
                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
         assertThat(sessionId).isNotEmpty();
     }
 
+    // Test adb staged installation wait for session to be ready by default
     @Test
-    public void testAdbStagedInstallNoWaitFlagWorks() throws Exception {
+    public void testAdbStagedInstallWaitsTillReadyByDefault() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
 
-        File apexFile = mTestUtils.getTestFile(SHIM_V2);
-        String output = getDevice().executeAdbCommand("install", "--staged",
-                "--no-wait", apexFile.getAbsolutePath());
+        final File apexFile = mTestUtils.getTestFile(SHIM_V2);
+        final String output = getDevice().executeAdbCommand("install", "--staged",
+                apexFile.getAbsolutePath());
+        assertThat(output).contains("Reboot device to apply staged session");
+        final String sessionId = getDevice().executeShellCommand(
+                "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+        assertThat(sessionId).isNotEmpty();
+    }
+
+    // Test we can skip waiting for staged session to be ready
+    @Test
+    public void testAdbStagedReadyWaitCanBeSkipped() throws Exception {
+        assumeTrue("Device does not support updating APEX",
+                mHostUtils.isApexUpdateSupported());
+
+        final File apexFile = mTestUtils.getTestFile(SHIM_V2);
+        final String output = getDevice().executeAdbCommand("install", "--staged",
+                "--staged-ready-timeout", "0", apexFile.getAbsolutePath());
         assertThat(output).doesNotContain("Reboot device to apply staged session");
         assertThat(output).contains("Success");
-        String sessionId = getDevice().executeShellCommand(
+        final String sessionId = getDevice().executeShellCommand(
                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
         assertThat(sessionId).isEmpty();
     }
 
+    // Test rollback-app command waits for staged sessions to be ready
+    @Test
+    public void testAdbRollbackAppWaitsForStagedReady() throws Exception {
+        assumeTrue("Device does not support updating APEX",
+                mHostUtils.isApexUpdateSupported());
+
+        final File apexFile = mTestUtils.getTestFile(SHIM_V2);
+        String output = getDevice().executeAdbCommand("install", "--staged",
+                "--enable-rollback", apexFile.getAbsolutePath());
+        assertThat(output).contains("Reboot device to apply staged session");
+        getDevice().reboot();
+        output = getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
+        assertThat(output).contains("Reboot device to apply staged session");
+        final String sessionId = getDevice().executeShellCommand(
+                "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+        assertThat(sessionId).isNotEmpty();
+    }
+
     @Test
     public void testAdbInstallMultiPackageCommandWorks() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
 
-        File apexFile = mTestUtils.getTestFile(SHIM_V2);
-        File apkFile = mTestUtils.getTestFile(APK_A);
-        String output = getDevice().executeAdbCommand("install-multi-package",
+        final File apexFile = mTestUtils.getTestFile(SHIM_V2);
+        final File apkFile = mTestUtils.getTestFile(APK_A);
+        final String output = getDevice().executeAdbCommand("install-multi-package",
                 apexFile.getAbsolutePath(), apkFile.getAbsolutePath());
         assertThat(output).contains("Created parent session");
         assertThat(output).contains("Created child session");
@@ -200,18 +241,40 @@
         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");
+        final ProcessInfo oldPs = getDevice().getProcessByName("system_server");
 
         getDevice().enableAdbRoot(); // Need root to restart system server
         assertThat(getDevice().executeShellCommand("am restart")).contains("Restart the system");
         getDevice().disableAdbRoot();
 
         // Wait for new system server process to start
-        long start = System.currentTimeMillis();
+        final long start = System.currentTimeMillis();
         while (System.currentTimeMillis() < start + SYSTEM_SERVER_TIMEOUT_MS) {
-            ProcessInfo newPs = getDevice().getProcessByName("system_server");
+            final ProcessInfo newPs = getDevice().getProcessByName("system_server");
             if (newPs != null) {
                 if (newPs.getPid() != oldPs.getPid()) {
                     getDevice().waitForDeviceAvailable();
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/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a3673df..bcb5c99 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1199,7 +1199,7 @@
         MockitoAnnotations.initMocks(this);
         when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
 
-        when(mUserManager.getUsers(eq(true))).thenReturn(
+        when(mUserManager.getAliveUsers()).thenReturn(
                 Arrays.asList(new UserInfo[] {
                         new UserInfo(VPN_USER, "", 0),
                 }));
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index ab12ac0..de35f91 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -26,19 +26,14 @@
 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.net.INetd.PERMISSION_INTERNET;
-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.SYSTEM_UID;
 
 import static com.android.server.connectivity.PermissionMonitor.NETWORK;
 import static com.android.server.connectivity.PermissionMonitor.SYSTEM;
-import static com.android.server.connectivity.PermissionMonitor.UidNetdPermissionInfo;
 
 import static junit.framework.Assert.fail;
 
@@ -69,7 +64,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -102,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";
@@ -127,12 +123,11 @@
         MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
-        when(mUserManager.getUsers(eq(true))).thenReturn(
+        when(mUserManager.getAliveUsers()).thenReturn(
                 Arrays.asList(new UserInfo[] {
                         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));
 
@@ -145,15 +140,35 @@
         verify(mMockPmi).getPackageList(mPermissionMonitor);
     }
 
-    private boolean wouldBeCarryoverPackage(String partition, int targetSdkVersion, int uid) {
-        final PackageInfo packageInfo = buildPackageInfo(partition, uid, MOCK_USER1);
+    private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid,
+            String... permissions) {
+        final PackageInfo packageInfo =
+                packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition);
         packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
-        return mPermissionMonitor.isCarryoverPackage(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,145 +185,168 @@
         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));
     }
 
-    /**
-     * Remove all permissions from the uid then setup permissions to uid for checking restricted
-     * network permission.
-     */
-    private void assertRestrictedNetworkPermission(boolean hasPermission, int uid,
-            String... permissions) {
-        removeAllPermissions(uid);
-        addPermissions(uid, permissions);
-        assertEquals(hasPermission, mPermissionMonitor.hasRestrictedNetworkPermission(uid));
+    @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() {
-        assertRestrictedNetworkPermission(false, MOCK_UID1);
-        assertRestrictedNetworkPermission(false, MOCK_UID1, CHANGE_NETWORK_STATE);
-        assertRestrictedNetworkPermission(true, MOCK_UID1, NETWORK_STACK);
-        assertRestrictedNetworkPermission(false, MOCK_UID1, CONNECTIVITY_INTERNAL);
-        assertRestrictedNetworkPermission(true, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
-        assertRestrictedNetworkPermission(false, MOCK_UID1, CHANGE_WIFI_STATE);
-        assertRestrictedNetworkPermission(true, MOCK_UID1, PERMISSION_MAINLINE_NETWORK_STACK);
+        assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
 
-        assertFalse(mPermissionMonitor.hasRestrictedNetworkPermission(MOCK_UID2));
-        assertFalse(mPermissionMonitor.hasRestrictedNetworkPermission(SYSTEM_UID));
+        assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL));
     }
 
     @Test
-    public void testIsCarryoverPackage() {
+    public void testHasRestrictedNetworkPermissionSystemUid() {
         doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt();
-        assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+        assertTrue(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_INTERNAL));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
 
         doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
-        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+        assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_INTERNAL));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+    }
 
-        assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, SYSTEM_UID));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, MOCK_UID1));
-        assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, MOCK_UID1));
+    @Test
+    public void testHasRestrictedNetworkPermissionVendorApp() {
+        assertTrue(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_P, MOCK_UID1, NETWORK_STACK));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
+
+        assertFalse(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE));
     }
 
     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 {
-        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID1));
-        assertBackgroundPermission(false, "mock1", MOCK_UID1);
-        assertBackgroundPermission(false, "mock2", MOCK_UID1, CONNECTIVITY_INTERNAL);
-        assertBackgroundPermission(true, "mock3", MOCK_UID1, NETWORK_STACK);
+        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID));
+        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_UID2));
-        assertBackgroundPermission(false, "mock4", MOCK_UID2);
-        assertBackgroundPermission(true, "mock5", MOCK_UID2,
+        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID1));
+        assertBackgroundPermission(false, MOCK_PACKAGE1, MOCK_UID1);
+        assertBackgroundPermission(true, MOCK_PACKAGE1, MOCK_UID1,
                 CONNECTIVITY_USE_RESTRICTED_NETWORKS);
 
-        doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
-        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID));
-        assertBackgroundPermission(false, "system1", SYSTEM_UID);
-        assertBackgroundPermission(true, "system2", SYSTEM_UID, CHANGE_NETWORK_STATE);
-        doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt();
-        removeAllPermissions(SYSTEM_UID);
-        assertBackgroundPermission(true, "system3", SYSTEM_UID);
+        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID2));
+        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 {
@@ -318,7 +356,7 @@
             // Add hook to verify and track result of setPermission.
             doAnswer((InvocationOnMock invocation) -> {
                 final Object[] args = invocation.getArguments();
-                final Boolean isSystem = args[0].equals(PERMISSION_SYSTEM);
+                final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
                 for (final int uid : (int[]) args[1]) {
                     // TODO: Currently, permission monitor will send duplicate commands for each uid
                     // corresponding to each user. Need to fix that and uncomment below test.
@@ -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));
@@ -561,48 +596,47 @@
         // SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission.
         // SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission.
 
-        final SparseArray<UidNetdPermissionInfo> uidsPermInfo = new SparseArray<>();
-        uidsPermInfo.put(MOCK_UID1, new UidNetdPermissionInfo(PERMISSION_INTERNET));
-        uidsPermInfo.put(MOCK_UID2, new UidNetdPermissionInfo(PERMISSION_NONE));
-        uidsPermInfo.put(SYSTEM_UID1, new UidNetdPermissionInfo(
-                PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS));
-        uidsPermInfo.put(SYSTEM_UID2, new UidNetdPermissionInfo(PERMISSION_UPDATE_DEVICE_STATS));
+        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+        netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET);
+        netdPermissionsAppIds.put(MOCK_UID2, INetd.PERMISSION_NONE);
+        netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+        netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS);
 
         // Send the permission information to netd, expect permission updated.
-        mPermissionMonitor.sendPackagePermissionsToNetd(uidsPermInfo);
+        mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds);
 
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET,
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET,
                 new int[]{MOCK_UID1});
-        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{MOCK_UID2});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
-        mNetdServiceMonitor.expectPermission(PERMISSION_UPDATE_DEVICE_STATS,
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID2});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS,
                 new int[]{SYSTEM_UID2});
 
         // Update permission of MOCK_UID1, expect new permission show up.
-        mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1, new UidNetdPermissionInfo(
-                PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS));
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1,
+                INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Change permissions of SYSTEM_UID2, expect new permission show up and old permission
         // revoked.
-        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2, new UidNetdPermissionInfo(
-                PERMISSION_INTERNET));
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2,
+                INetd.PERMISSION_INTERNET);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
 
         // Revoke permission from SYSTEM_UID1, expect no permission stored.
-        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, new UidNetdPermissionInfo(
-                PERMISSION_NONE));
-        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{SYSTEM_UID1});
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.PERMISSION_NONE);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
     }
 
     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;
     }
 
@@ -618,30 +652,31 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID2});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2});
     }
 
     @Test
     public void testPackageInstallSharedUid() throws Exception {
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
-        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        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});
         mPermissionMonitor.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -649,12 +684,12 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        mNetdServiceMonitor.expectPermission(PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -662,16 +697,15 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | 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(PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -679,10 +713,10 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {});
-        mNetdServiceMonitor.expectPermission(PERMISSION_NONE, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1});
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -690,19 +724,17 @@
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
-        mNetdServiceMonitor.expectPermission(PERMISSION_INTERNET
-                | PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | 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(PERMISSION_INTERNET, new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
 
     @Test
@@ -711,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 e8c4ee9..c76b4cd 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -1238,15 +1238,14 @@
          * @see UserManagerService#getUsers(boolean)
          */
         doAnswer(invocation -> {
-            final boolean excludeDying = (boolean) invocation.getArguments()[0];
             final ArrayList<UserInfo> result = new ArrayList<>(users.length);
             for (UserInfo ui : users) {
-                if (!excludeDying || (ui.isEnabled() && !ui.partial)) {
+                if (ui.isEnabled() && !ui.partial) {
                     result.add(ui);
                 }
             }
             return result;
-        }).when(mUserManager).getUsers(anyBoolean());
+        }).when(mUserManager).getAliveUsers();
 
         doAnswer(invocation -> {
             final int id = (int) invocation.getArguments()[0];
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/tools/preload-check/Android.bp b/tools/preload-check/Android.bp
index 87b31d2..aaa6d76 100644
--- a/tools/preload-check/Android.bp
+++ b/tools/preload-check/Android.bp
@@ -15,7 +15,7 @@
 java_test_host {
     name: "PreloadCheck",
     srcs: ["src/**/*.java"],
-    java_resources: [":preloaded-classes-blacklist"],
+    java_resources: [":preloaded-classes-denylist"],
     libs: ["tradefed"],
     test_suites: ["general-tests"],
     required: ["preload-check-device"],
diff --git a/tools/preload-check/src/com/android/preload/check/PreloadCheck.java b/tools/preload-check/src/com/android/preload/check/PreloadCheck.java
index 00fd414e3..3d85153 100644
--- a/tools/preload-check/src/com/android/preload/check/PreloadCheck.java
+++ b/tools/preload-check/src/com/android/preload/check/PreloadCheck.java
@@ -69,13 +69,13 @@
     }
 
     /**
-     * Test the classes mentioned in the embedded preloaded-classes blacklist.
+     * Test the classes mentioned in the embedded preloaded-classes denylist.
      */
     @Test
-    public void testBlackList() throws Exception {
+    public void testDenyList() throws Exception {
         StringBuilder sb = new StringBuilder();
         try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass()
-                .getResourceAsStream("/preloaded-classes-blacklist")))) {
+                .getResourceAsStream("/preloaded-classes-denylist")))) {
             String s;
             while ((s = br.readLine()) != null) {
                 s = s.trim();
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/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/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);
     }