Merge "Fix aapt2 dump xmltree not escape text nodes"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 1d92778..9a4323a 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,6 +25,6 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES}
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java
new file mode 100644
index 0000000..6b739bc
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.perftests;
+
+import static android.graphics.PathIterator.VERB_DONE;
+
+import android.graphics.Path;
+import android.graphics.PathIterator;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class PathIteratorPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private Path constructCircularPath(int numSegments) {
+        Path path = new Path();
+        float angleIncrement = (float) (2 * Math.PI / numSegments);
+        float radius = 200f;
+        float prevX = 0f, prevY = 0f;
+        float angle = 0f;
+        for (int i = 0; i <= numSegments; ++i) {
+            float x = (float) Math.cos(angle) * radius;
+            float y = (float) Math.sin(angle) * radius;
+            if (i > 0) {
+                path.cubicTo(prevX, prevY, x, y, x, y);
+            } else {
+                path.moveTo(x, y);
+            }
+            prevX = x;
+            prevY = y;
+            angle += angleIncrement;
+        }
+        return path;
+    }
+
+    private void testNextSegmentImpl(int numSegments) {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = constructCircularPath(numSegments);
+        while (state.keepRunning()) {
+            PathIterator iterator = path.iterator();
+            PathIterator.Segment segment = iterator.next();
+            while (segment.getVerb() != VERB_DONE) {
+                segment = iterator.next();
+            }
+        }
+    }
+
+    private void testNextFloatsImpl(int numSegments) {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        float[] points = new float[8];
+        Path path = constructCircularPath(numSegments);
+        while (state.keepRunning()) {
+            PathIterator iterator = path.iterator();
+            int verb = iterator.next(points, 0);
+            while (verb != VERB_DONE) {
+                verb = iterator.next(points, 0);
+            }
+        }
+    }
+
+    @Test
+    public void testNextSegment10() {
+        testNextSegmentImpl(10);
+    }
+
+    @Test
+    public void testNextSegment100() {
+        testNextSegmentImpl(100);
+    }
+
+    @Test
+    public void testNextSegment1000() {
+        testNextSegmentImpl(1000);
+    }
+
+    @Test
+    public void testNextArray10() {
+        testNextFloatsImpl(10);
+    }
+
+    @Test
+    public void testNextArray100() {
+        testNextFloatsImpl(100);
+    }
+
+    @Test
+    public void testNextArray1000() {
+        testNextFloatsImpl(1000);
+    }
+
+}
diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
index 0f3a068..53e5d99 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
@@ -44,7 +44,7 @@
         <option name="hidden-api-checks" value="false"/>
 
         <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
-        <option name="device-listeners" value="android.device.collectors.PerfettoListener,android.device.collectors.SimpleperfListener" />
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener,android.device.collectors.SimpleperfListener" />
 
         <!-- 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" />
@@ -58,7 +58,15 @@
         <option name="instrumentation-arg" key="arguments" value="&quot;&quot;" />
         <option name="instrumentation-arg" key="events_to_record" value="instructions,cpu-cycles,raw-l3d-cache-refill,sched:sched_waking" />
         <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger" />
-        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;android::SurfaceFlinger::commit(long, long, long)&quot;" />
+        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;android::SurfaceFlinger::commit(;android::SurfaceFlinger::composite(&quot;" />
+
+        <!-- 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" />
+
     </test>
 
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
index f4d0c05..45d164c 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
@@ -16,6 +16,7 @@
 
 package android.surfaceflinger;
 
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
@@ -23,14 +24,19 @@
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Random;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class SurfaceFlingerPerfTest {
@@ -48,19 +54,175 @@
     public void setup() {
         mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
     }
+
+    @After
+    public void teardown() {
+        mSurfaceControls.forEach(SurfaceControl::release);
+        mByfferTrackers.forEach(BufferFlinger::freeBuffers);
+    }
+
+
+    private ArrayList<BufferFlinger> mByfferTrackers = new ArrayList<>();
+    private BufferFlinger createBufferTracker(int color) {
+        BufferFlinger bufferTracker = new BufferFlinger(BUFFER_COUNT, color);
+        mByfferTrackers.add(bufferTracker);
+        return bufferTracker;
+    }
+
+    private ArrayList<SurfaceControl> mSurfaceControls = new ArrayList<>();
+    private SurfaceControl createSurfaceControl() throws InterruptedException {
+        SurfaceControl sc = mActivity.createChildSurfaceControl();
+        mSurfaceControls.add(sc);
+        return sc;
+    }
+
     @Test
-    public void submitSingleBuffer() throws Exception {
-        SurfaceControl sc = mActivity.getChildSurfaceControl();
+    public void singleBuffer() throws Exception {
+        SurfaceControl sc = createSurfaceControl();
+        BufferFlinger bufferTracker = createBufferTracker(Color.GREEN);
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        BufferFlinger bufferflinger = new BufferFlinger(BUFFER_COUNT, Color.GREEN);
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        bufferTracker.addBuffer(t, sc);
         t.show(sc);
 
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            bufferflinger.addBuffer(t, sc);
+            bufferTracker.addBuffer(t, sc);
             t.apply();
         }
-        bufferflinger.freeBuffers();
     }
-}
 
+    static int getRandomColorComponent() {
+        return new Random().nextInt(155) + 100;
+    }
+
+    @Test
+    public void multipleBuffers() throws Exception {
+        final int MAX_BUFFERS = 10;
+
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        for (int i = 0; i < MAX_BUFFERS; i++) {
+            SurfaceControl sc = createSurfaceControl();
+            BufferFlinger bufferTracker = createBufferTracker(Color.argb(getRandomColorComponent(),
+                    getRandomColorComponent(), getRandomColorComponent(),
+                    getRandomColorComponent()));
+            bufferTracker.addBuffer(t, sc);
+            t.setPosition(sc, i * 10, i * 10);
+            t.show(sc);
+        }
+        t.apply(true);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            for (int i = 0; i < MAX_BUFFERS; i++) {
+                mByfferTrackers.get(i).addBuffer(t, mSurfaceControls.get(i));
+            }
+            t.apply();
+        }
+    }
+
+    @Test
+    public void multipleOpaqueBuffers() throws Exception {
+        final int MAX_BUFFERS = 10;
+
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        for (int i = 0; i < MAX_BUFFERS; i++) {
+            SurfaceControl sc = createSurfaceControl();
+            BufferFlinger bufferTracker = createBufferTracker(Color.rgb(getRandomColorComponent(),
+                    getRandomColorComponent(), getRandomColorComponent()));
+            bufferTracker.addBuffer(t, sc);
+            t.setOpaque(sc, true);
+            t.setPosition(sc, i * 10, i * 10);
+            t.show(sc);
+        }
+        t.apply(true);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            for (int i = 0; i < MAX_BUFFERS; i++) {
+                mByfferTrackers.get(i).addBuffer(t, mSurfaceControls.get(i));
+            }
+            t.apply();
+        }
+    }
+
+    @Test
+    public void geometryChanges() throws Exception {
+        final int MAX_POSITION = 10;
+        final float MAX_SCALE = 2.0f;
+
+        SurfaceControl sc = createSurfaceControl();
+        BufferFlinger bufferTracker = createBufferTracker(Color.GREEN);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        bufferTracker.addBuffer(t, sc);
+        t.show(sc).apply(true);
+
+        int step = 0;
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            step = ++step % MAX_POSITION;
+            t.setPosition(sc, step, step);
+            float scale = ((step * MAX_SCALE) / MAX_POSITION) + 0.5f;
+            t.setScale(sc, scale, scale);
+            t.apply();
+        }
+    }
+
+    @Test
+    public void geometryWithBufferChanges() throws Exception {
+        final int MAX_POSITION = 10;
+
+        SurfaceControl sc = createSurfaceControl();
+        BufferFlinger bufferTracker = createBufferTracker(Color.GREEN);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        bufferTracker.addBuffer(t, sc);
+        t.show(sc).apply(true);
+
+        int step = 0;
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            step = ++step % MAX_POSITION;
+            t.setPosition(sc, step, step);
+            float scale = ((step * 2.0f) / MAX_POSITION) + 0.5f;
+            t.setScale(sc, scale, scale);
+            bufferTracker.addBuffer(t, sc);
+            t.apply();
+        }
+    }
+
+    @Test
+    public void addRemoveLayers() throws Exception {
+        SurfaceControl sc = createSurfaceControl();
+        BufferFlinger bufferTracker = createBufferTracker(Color.GREEN);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            SurfaceControl childSurfaceControl =  new SurfaceControl.Builder()
+                    .setName("childLayer").setBLASTLayer().build();
+            bufferTracker.addBuffer(t, childSurfaceControl);
+            t.reparent(childSurfaceControl, sc);
+            t.apply();
+            t.remove(childSurfaceControl).apply();
+        }
+    }
+
+    @Test
+    public void displayScreenshot() throws Exception {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Bitmap screenshot =
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
+            screenshot.recycle();
+        }
+    }
+
+    @Test
+    public void layerScreenshot() throws Exception {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Bitmap screenshot =
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot(
+                            mActivity.getWindow());
+            screenshot.recycle();
+        }
+    }
+
+}
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
index a9b2a31..832a0cd 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
@@ -43,7 +43,7 @@
         setContentView(mTestSurfaceView);
     }
 
-    public SurfaceControl getChildSurfaceControl() throws InterruptedException {
+    public SurfaceControl createChildSurfaceControl() throws InterruptedException {
         return mTestSurfaceView.getChildSurfaceControlHelper();
     }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index f49cdbf..ab0ac5a 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1312,6 +1312,9 @@
          * Calling this method will override any requirements previously defined
          * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
          * want to call one of these methods.
+         * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * {@link JobScheduler} may try to shift the execution of jobs requiring
+         * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network.
          * <p class="note">
          * When your job executes in
          * {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1742,6 +1745,7 @@
          *     <li>Bypass Doze, app standby, and battery saver network restrictions</li>
          *     <li>Be less likely to be killed than regular jobs</li>
          *     <li>Be subject to background location throttling</li>
+         *     <li>Be exempt from delay to optimize job execution</li>
          * </ol>
          *
          * <p>
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 632ecb2c..dfdb290 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -45,8 +45,23 @@
  * </p>
  * <p>
  * The framework will be intelligent about when it executes jobs, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
+ * and defer them as much as possible. Typically, if you don't specify a deadline on a job, it
  * can be run at any moment depending on the current state of the JobScheduler's internal queue.
+ * </p>
+ * <p>
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * JobScheduler may try to optimize job execution by shifting execution to times with more available
+ * system resources in order to lower user impact. Factors in system health include sufficient
+ * battery, idle, charging, and access to an un-metered network. Jobs will initially be treated as
+ * if they have all these requirements, but as their deadlines approach, restrictions will become
+ * less strict. Requested requirements will not be affected by this change.
+ * </p>
+ *
+ * {@see android.app.job.JobInfo.Builder#setRequiresBatteryNotLow(boolean)}
+ * {@see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}
+ * {@see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)}
+ * {@see android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}
+ *
  * <p>
  * While a job is running, the system holds a wakelock on behalf of your app.  For this reason,
  * you do not need to take any action to guarantee that the device stays awake for the
diff --git a/cmds/screencap/OWNERS b/cmds/screencap/OWNERS
new file mode 100644
index 0000000..89f1177
--- /dev/null
+++ b/cmds/screencap/OWNERS
@@ -0,0 +1,2 @@
+include /graphics/java/android/graphics/OWNERS
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index 72ee7f7..d519452 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10317,7 +10317,9 @@
     field public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
     field public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
     field public static final String ACTION_PROFILE_ACCESSIBLE = "android.intent.action.PROFILE_ACCESSIBLE";
+    field public static final String ACTION_PROFILE_ADDED = "android.intent.action.PROFILE_ADDED";
     field public static final String ACTION_PROFILE_INACCESSIBLE = "android.intent.action.PROFILE_INACCESSIBLE";
+    field public static final String ACTION_PROFILE_REMOVED = "android.intent.action.PROFILE_REMOVED";
     field public static final String ACTION_PROVIDER_CHANGED = "android.intent.action.PROVIDER_CHANGED";
     field public static final String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
     field public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
@@ -15122,7 +15124,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR;
   }
 
-  public class Path {
+  public class Path implements java.lang.Iterable<android.graphics.PathIterator.Segment> {
     ctor public Path();
     ctor public Path(@Nullable android.graphics.Path);
     method public void addArc(@NonNull android.graphics.RectF, float, float);
@@ -15145,13 +15147,18 @@
     method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
     method public void computeBounds(@NonNull android.graphics.RectF, boolean);
+    method public void conicTo(float, float, float, float, float);
     method public void cubicTo(float, float, float, float, float, float);
     method @NonNull public android.graphics.Path.FillType getFillType();
+    method public int getGenerationId();
     method public void incReserve(int);
+    method public boolean interpolate(@NonNull android.graphics.Path, float, @NonNull android.graphics.Path);
     method @Deprecated public boolean isConvex();
     method public boolean isEmpty();
+    method public boolean isInterpolatable(@NonNull android.graphics.Path);
     method public boolean isInverseFillType();
     method public boolean isRect(@Nullable android.graphics.RectF);
+    method @NonNull public android.graphics.PathIterator iterator();
     method public void lineTo(float, float);
     method public void moveTo(float, float);
     method public void offset(float, float, @Nullable android.graphics.Path);
@@ -15159,6 +15166,7 @@
     method public boolean op(@NonNull android.graphics.Path, @NonNull android.graphics.Path.Op);
     method public boolean op(@NonNull android.graphics.Path, @NonNull android.graphics.Path, @NonNull android.graphics.Path.Op);
     method public void quadTo(float, float, float, float);
+    method public void rConicTo(float, float, float, float, float);
     method public void rCubicTo(float, float, float, float, float, float);
     method public void rLineTo(float, float);
     method public void rMoveTo(float, float);
@@ -15207,6 +15215,27 @@
     ctor public PathEffect();
   }
 
+  public class PathIterator implements java.util.Iterator<android.graphics.PathIterator.Segment> {
+    method public boolean hasNext();
+    method @NonNull public int next(@NonNull float[], int);
+    method @NonNull public android.graphics.PathIterator.Segment next();
+    method @NonNull public int peek();
+    field public static final int VERB_CLOSE = 5; // 0x5
+    field public static final int VERB_CONIC = 3; // 0x3
+    field public static final int VERB_CUBIC = 4; // 0x4
+    field public static final int VERB_DONE = 6; // 0x6
+    field public static final int VERB_LINE = 1; // 0x1
+    field public static final int VERB_MOVE = 0; // 0x0
+    field public static final int VERB_QUAD = 2; // 0x2
+  }
+
+  public static class PathIterator.Segment {
+    ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
+    method public float getConicWeight();
+    method @NonNull public float[] getPoints();
+    method @NonNull public int getVerb();
+  }
+
   public class PathMeasure {
     ctor public PathMeasure();
     ctor public PathMeasure(android.graphics.Path, boolean);
@@ -51921,6 +51950,7 @@
     method public void addChild(android.view.View, int);
     method public boolean canOpenPopup();
     method public int describeContents();
+    method public void enableQueryFromAppProcess(@NonNull android.view.View);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(@NonNull String);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -51988,7 +52018,6 @@
     method public boolean isTextEntryKey();
     method public boolean isTextSelectable();
     method public boolean isVisibleToUser();
-    method public void makeQueryableFromAppProcess(@NonNull android.view.View);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ef8ae70..a87f2e2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6134,6 +6134,11 @@
     field public static final int ROLE_OUTPUT = 2; // 0x2
   }
 
+  public class AudioDeviceVolumeManager {
+    ctor public AudioDeviceVolumeManager(@NonNull android.content.Context);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+  }
+
   public final class AudioFocusInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.media.AudioAttributes getAttributes();
@@ -6453,6 +6458,33 @@
     method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
   }
 
+  public final class VolumeInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public static android.media.VolumeInfo getDefaultVolumeInfo();
+    method public int getMaxVolumeIndex();
+    method public int getMinVolumeIndex();
+    method public int getStreamType();
+    method @Nullable public android.media.audiopolicy.AudioVolumeGroup getVolumeGroup();
+    method public int getVolumeIndex();
+    method public boolean hasStreamType();
+    method public boolean hasVolumeGroup();
+    method public boolean isMuted();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.VolumeInfo> CREATOR;
+    field public static final int INDEX_NOT_SET = -100; // 0xffffff9c
+  }
+
+  public static final class VolumeInfo.Builder {
+    ctor public VolumeInfo.Builder(int);
+    ctor public VolumeInfo.Builder(@NonNull android.media.audiopolicy.AudioVolumeGroup);
+    ctor public VolumeInfo.Builder(@NonNull android.media.VolumeInfo);
+    method @NonNull public android.media.VolumeInfo build();
+    method @NonNull public android.media.VolumeInfo.Builder setMaxVolumeIndex(int);
+    method @NonNull public android.media.VolumeInfo.Builder setMinVolumeIndex(int);
+    method @NonNull public android.media.VolumeInfo.Builder setMuted(boolean);
+    method @NonNull public android.media.VolumeInfo.Builder setVolumeIndex(int);
+  }
+
 }
 
 package android.media.audiofx {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bb6cc45..53014a3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3391,6 +3391,7 @@
     method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
     method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken);
     method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
+    method @NonNull public android.window.WindowContainerTransaction requestFocusOnTaskFragment(@NonNull android.os.IBinder);
     method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index b9ad595..7215987 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -753,7 +753,7 @@
 
     /**
      * Returns the vibration pattern for notifications posted to this channel. Will be ignored if
-     * vibration is not enabled ({@link #shouldVibrate()}.
+     * vibration is not enabled ({@link #shouldVibrate()}).
      */
     public long[] getVibrationPattern() {
         return mVibration;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6866510..c12e26b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -10802,14 +10803,19 @@
      */
     public Intent createAdminSupportIntent(@NonNull String restriction) {
         throwIfParentInstance("createAdminSupportIntent");
+        Intent result = null;
         if (mService != null) {
             try {
-                return mService.createAdminSupportIntent(restriction);
+                result = mService.createAdminSupportIntent(restriction);
+                if (result != null) {
+                    result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+                            mContext.getAttributionSource());
+                }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
-        return null;
+        return result;
     }
 
     /**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0e0b2dc..f593ed9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4092,6 +4092,45 @@
             "android.intent.action.PROFILE_INACCESSIBLE";
 
     /**
+     * Broadcast sent to the parent user when an associated profile is removed.
+     * Carries an extra {@link #EXTRA_USER} that specifies the {@link UserHandle} of the profile
+     * that was removed.
+     *
+     * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
+     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
+     * managed user is removed.
+     *
+     * <p>Only applications (for example Launchers) that need to display merged content across both
+     * the parent user and its associated profiles need to worry about this broadcast.
+     * This is only sent to registered receivers created with {@link Context#registerReceiver}.
+     * It is not sent to manifest receivers.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PROFILE_REMOVED =
+            "android.intent.action.PROFILE_REMOVED";
+
+    /**
+     * Broadcast sent to the parent user when an associated profile is added (the profile was
+     * created and is ready to be used).
+     * Carries an extra {@link #EXTRA_USER} that specifies the  {@link UserHandle} of the profile
+     * that was added.
+     *
+     * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
+     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
+     * managed user is added.
+     *
+     * <p>Only applications (for example Launchers) that need to display merged content across both
+     * the parent user and its associated profiles need to worry about this broadcast.
+     * This is only sent to registered receivers created with {@link Context#registerReceiver}.
+     * It is not sent to manifest receivers.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PROFILE_ADDED =
+            "android.intent.action.PROFILE_ADDED";
+
+    /**
      * Broadcast sent to the system user when the 'device locked' state changes for any user.
      * Carries an extra {@link #EXTRA_USER_HANDLE} that specifies the ID of the user for which
      * the device was locked or unlocked.
@@ -7124,12 +7163,18 @@
      */
     private static final int LOCAL_FLAG_FROM_URI = 1 << 4;
 
+    /**
+     * Local flag indicating this instance was created by the system.
+     */
+    /** @hide */
+    public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // toUri() and parseUri() options.
 
     /** @hide */
-    @IntDef(flag = true, prefix = { "URI_" }, value = {
+    @IntDef(flag = true, prefix = {"URI_"}, value = {
             URI_ALLOW_UNSAFE,
             URI_ANDROID_APP_SCHEME,
             URI_INTENT_SCHEME,
@@ -10535,7 +10580,9 @@
         // delivered Intent then it would have been reported when that Intent left the sending
         // process.
         if ((src.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0
-                && (src.mLocalFlags & LOCAL_FLAG_FROM_PROTECTED_COMPONENT) == 0) {
+                && (src.mLocalFlags & (
+                        LOCAL_FLAG_FROM_PROTECTED_COMPONENT
+                                | LOCAL_FLAG_FROM_SYSTEM)) == 0) {
             mLocalFlags |= LOCAL_FLAG_UNFILTERED_EXTRAS;
         }
         return this;
@@ -11878,13 +11925,14 @@
         // Detect cases where we're about to launch a potentially unsafe intent
         if (StrictMode.vmUnsafeIntentLaunchEnabled()) {
             if ((mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0
-                    && (mLocalFlags & LOCAL_FLAG_FROM_PROTECTED_COMPONENT) == 0) {
+                    && (mLocalFlags
+                    & (LOCAL_FLAG_FROM_PROTECTED_COMPONENT | LOCAL_FLAG_FROM_SYSTEM)) == 0) {
                 StrictMode.onUnsafeIntentLaunch(this);
             } else if ((mLocalFlags & LOCAL_FLAG_UNFILTERED_EXTRAS) != 0) {
                 StrictMode.onUnsafeIntentLaunch(this);
             } else if ((mLocalFlags & LOCAL_FLAG_FROM_URI) != 0
                     && !(mCategories != null && mCategories.contains(CATEGORY_BROWSABLE)
-                        && mComponent == null)) {
+                    && mComponent == null)) {
                 // Since the docs for #URI_ALLOW_UNSAFE recommend setting the category to browsable
                 // for an implicit Intent parsed from a URI a violation should be reported if these
                 // conditions are not met.
@@ -11897,6 +11945,17 @@
      * @hide
      */
     public void prepareToEnterProcess(boolean fromProtectedComponent, AttributionSource source) {
+        if (fromProtectedComponent) {
+            prepareToEnterProcess(LOCAL_FLAG_FROM_PROTECTED_COMPONENT, source);
+        } else {
+            prepareToEnterProcess(0, source);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void prepareToEnterProcess(int localFlags, AttributionSource source) {
         // We just entered destination process, so we should be able to read all
         // parcelables inside.
         setDefusable(true);
@@ -11904,13 +11963,15 @@
         if (mSelector != null) {
             // We can't recursively claim that this data is from a protected
             // component, since it may have been filled in by a malicious app
-            mSelector.prepareToEnterProcess(false, source);
+            mSelector.prepareToEnterProcess(0, source);
         }
         if (mClipData != null) {
             mClipData.prepareToEnterProcess(source);
         }
         if (mOriginalIntent != null) {
-            mOriginalIntent.prepareToEnterProcess(false, source);
+            // We can't recursively claim that this data is from a protected
+            // component, since it may have been filled in by a malicious app
+            mOriginalIntent.prepareToEnterProcess(0, source);
         }
 
         if (mContentUserHint != UserHandle.USER_CURRENT) {
@@ -11920,9 +11981,7 @@
             }
         }
 
-        if (fromProtectedComponent) {
-            mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
-        }
+        mLocalFlags |= localFlags;
 
         // Special attribution fix-up logic for any BluetoothDevice extras
         // passed via Bluetooth intents
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 885eb70..ffd80ea 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
+
 import android.annotation.SystemService;
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
@@ -487,14 +489,19 @@
     }
 
     public Intent createLocalApprovalIntent() {
+        Intent result = null;
         try {
             if (mService != null) {
-                return mService.createLocalApprovalIntent();
+                result = mService.createLocalApprovalIntent();
+                if (result != null) {
+                    result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+                            mContext.getAttributionSource());
+                }
             }
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
-        return null;
+        return result;
     }
 
     /**
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 70b90e6..e48a02a 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -15,6 +15,8 @@
  */
 package android.content.pm;
 
+import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
+
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -628,7 +630,12 @@
         try {
             mService.createShortcutResultIntent(mContext.getPackageName(),
                     shortcut, injectMyUserId(), ret);
-            return getFutureOrThrow(ret);
+            Intent result = getFutureOrThrow(ret);
+            if (result != null) {
+                result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+                        mContext.getAttributionSource());
+            }
+            return result;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 55cab52..2e8e763 100644
--- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -23,5 +23,8 @@
  * @hide
  */
 oneway interface IBiometricContextListener {
-    void onDozeChanged(boolean isDozing);
+    // Called when doze or awake (screen on) status changes.
+    // These may be called while the device is still transitioning to the new state
+    // (i.e. about to become awake or enter doze)
+    void onDozeChanged(boolean isDozing, boolean isAwake);
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 48b9b88..3a157d3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -107,6 +107,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.MotionEvent.ToolType;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
@@ -947,7 +948,7 @@
          * @hide
          */
         @Override
-        public void updateEditorToolType(int toolType) {
+        public void updateEditorToolType(@ToolType int toolType) {
             onUpdateEditorToolType(toolType);
         }
 
@@ -3079,10 +3080,13 @@
      * {@link MotionEvent#getToolType(int)} was used to click the editor.
      * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
      * companion widget instead of normal virtual keyboard.
+     * <p> This method is called after {@link #onStartInput(EditorInfo, boolean)} and before
+     * {@link #onStartInputView(EditorInfo, boolean)} when editor was clicked with a known tool
+     * type.</p>
      * <p> Default implementation does nothing. </p>
      * @param toolType what {@link MotionEvent#getToolType(int)} was used to click on editor.
      */
-    public void onUpdateEditorToolType(int toolType) {
+    public void onUpdateEditorToolType(@ToolType int toolType) {
         // Intentionally empty
     }
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 43a6be5..fb8f84a 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -469,7 +469,15 @@
      * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and
      *    Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the
      *    “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it
-     *    forces a choice; otherwise ...
+     *    forces a choice;
+     *      - Workaround Note: ANGLE and Vulkan currently have issues with applications that use YUV
+     *        target functionality.  The ANGLE broadcast receiver code will apply a "deferlist" at
+     *        the first boot of a newly-flashed device.  However, there is a gap in time between
+     *        when applications can start and when the deferlist is applied.  For now, assume that
+     *        if ANGLE is the system driver and Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS is
+     *        empty, that the deferlist has not yet been applied.  In this case, select the Legacy
+     *        driver.
+     *    otherwise ...
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ...
      * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy;
      *    a.k.a. mAngleIsSystemDriver, which is set by the device’s “ro.hardware.egl” property)
@@ -509,9 +517,16 @@
         final List<String> optInValues = getGlobalSettingsString(
                 contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
         Log.v(TAG, "Currently set values for:");
-        Log.v(TAG, "  angle_gl_driver_selection_pkgs = " + optInPackages);
+        Log.v(TAG, "    angle_gl_driver_selection_pkgs =" + optInPackages);
         Log.v(TAG, "  angle_gl_driver_selection_values =" + optInValues);
 
+        // If ANGLE is the system driver AND the deferlist has not yet been applied, select the
+        // Legacy driver
+        if (mAngleIsSystemDriver && optInPackages.size() <= 1) {
+            Log.v(TAG, "Ignoring angle_gl_driver_selection_* until deferlist has been applied");
+            return ANGLE_GL_DRIVER_TO_USE_LEGACY;
+        }
+
         // Make sure we have good settings to use
         if (optInPackages.size() != optInValues.size()) {
             Log.w(TAG,
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 06930bb..392bf4e 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -145,7 +145,8 @@
      */
     @IntDef(prefix = { "FLAG_" }, flag = true, value = {
             FLAG_BYPASS_INTERRUPTION_POLICY,
-            FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+            FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
+            FLAG_INVALIDATE_SETTINGS_CACHE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flag{}
@@ -182,7 +183,7 @@
      *
      * @hide
      */
-    public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x3;
+    public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x4;
 
     /**
      * All flags supported by vibrator service, update it when adding new flag.
diff --git a/core/java/android/text/GraphemeClusterSegmentIterator.java b/core/java/android/text/GraphemeClusterSegmentIterator.java
new file mode 100644
index 0000000..e3976a7
--- /dev/null
+++ b/core/java/android/text/GraphemeClusterSegmentIterator.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Paint;
+
+/**
+ * Implementation of {@code SegmentIterator} using grapheme clusters as the text segment. Whitespace
+ * characters are included as segments.
+ *
+ * @hide
+ */
+public class GraphemeClusterSegmentIterator extends SegmentIterator {
+    private final CharSequence mText;
+    private final TextPaint mTextPaint;
+
+    public GraphemeClusterSegmentIterator(
+            @NonNull CharSequence text, @NonNull TextPaint textPaint) {
+        mText = text;
+        mTextPaint = textPaint;
+    }
+
+    @Override
+    public int previousStartBoundary(@IntRange(from = 0) int offset) {
+        int boundary = mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
+        return boundary == -1 ? DONE : boundary;
+    }
+
+    @Override
+    public int previousEndBoundary(@IntRange(from = 0) int offset) {
+        int boundary = mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
+        // Check that there is another cursor position before, otherwise this is not a valid
+        // end boundary.
+        if (mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, boundary, Paint.CURSOR_BEFORE) == -1) {
+            return DONE;
+        }
+        return boundary == -1 ? DONE : boundary;
+    }
+
+    @Override
+    public int nextStartBoundary(@IntRange(from = 0) int offset) {
+        int boundary = mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
+        // Check that there is another cursor position after, otherwise this is not a valid
+        // start boundary.
+        if (mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, boundary, Paint.CURSOR_AFTER) == -1) {
+            return DONE;
+        }
+        return boundary == -1 ? DONE : boundary;
+    }
+
+    @Override
+    public int nextEndBoundary(@IntRange(from = 0) int offset) {
+        int boundary = mTextPaint.getTextRunCursor(
+                mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
+        return boundary == -1 ? DONE : boundary;
+    }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4efc838..b5f7c54 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -19,11 +19,13 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
 import android.text.method.TextKeyListener;
@@ -710,8 +712,7 @@
     }
 
     /**
-     * Return the start position of the line, given the left and right bounds
-     * of the margins.
+     * Return the start position of the line, given the left and right bounds of the margins.
      *
      * @param line the line index
      * @param left the left bounds (0, or leading margin if ltr para)
@@ -1312,6 +1313,38 @@
         return horizontal;
     }
 
+    private void fillHorizontalBoundsForLine(int line, float[] horizontalBounds) {
+        final int lineStart = getLineStart(line);
+        final int lineEnd = getLineEnd(line);
+        final int lineLength = lineEnd - lineStart;
+
+        final int dir = getParagraphDirection(line);
+        final Directions directions = getLineDirections(line);
+
+        final boolean hasTab = getLineContainsTab(line);
+        TabStops tabStops = null;
+        if (hasTab && mText instanceof Spanned) {
+            // Just checking this line should be good enough, tabs should be
+            // consistent across all lines in a paragraph.
+            TabStopSpan[] tabs =
+                    getParagraphSpans((Spanned) mText, lineStart, lineEnd, TabStopSpan.class);
+            if (tabs.length > 0) {
+                tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+            }
+        }
+
+        final TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops,
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
+        if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) {
+            horizontalBounds = new float[2 * lineLength];
+        }
+
+        tl.measureAllBounds(horizontalBounds, null);
+        TextLine.recycle(tl);
+    }
+
     /**
      * Return the characters' bounds in the given range. The {@code bounds} array will be filled
      * starting from {@code boundsStart} (inclusive). The coordinates are in local text layout.
@@ -1358,32 +1391,11 @@
             final int lineStart = getLineStart(line);
             final int lineEnd = getLineEnd(line);
             final int lineLength = lineEnd - lineStart;
-
-            final int dir = getParagraphDirection(line);
-            final boolean hasTab = getLineContainsTab(line);
-            final Directions directions = getLineDirections(line);
-
-            TabStops tabStops = null;
-            if (hasTab && mText instanceof Spanned) {
-                // Just checking this line should be good enough, tabs should be
-                // consistent across all lines in a paragraph.
-                TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, lineStart, lineEnd,
-                        TabStopSpan.class);
-                if (tabs.length > 0) {
-                    tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
-                }
-            }
-
-            final TextLine tl = TextLine.obtain();
-            tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops,
-                    getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
-                    isFallbackLineSpacingEnabled());
             if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) {
                 horizontalBounds = new float[2 * lineLength];
             }
+            fillHorizontalBoundsForLine(line, horizontalBounds);
 
-            tl.measureAllBounds(horizontalBounds, null);
-            TextLine.recycle(tl);
             final int lineLeft = getParagraphLeft(line);
             final int lineRight = getParagraphRight(line);
             final int lineStartPos = getLineStartPos(line, lineLeft, lineRight);
@@ -1802,6 +1814,380 @@
     }
 
     /**
+     * Finds the range of text which is inside the specified rectangle area. The start of the range
+     * is the start of the first text segment inside the area, and the end of the range is the end
+     * of the last text segment inside the area.
+     *
+     * <p>A text segment is considered to be inside the area if the center of its bounds is inside
+     * the area. If a text segment spans multiple lines or multiple directional runs (e.g. a
+     * hyphenated word), the text segment is divided into pieces at the line and run breaks, then
+     * the text segment is considered to be inside the area if any of its pieces have their center
+     * inside the area.
+     *
+     * <p>The returned range may also include text segments which are not inside the specified area,
+     * if those text segments are in between text segments which are inside the area. For example,
+     * the returned range may be "segment1 segment2 segment3" if "segment1" and "segment3" are
+     * inside the area and "segment2" is not.
+     *
+     * @param area area for which the text range will be found
+     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
+     *     segment
+     * @return int array of size 2 containing the start (inclusive) and end (exclusive) character
+     *     offsets of the range, or null if there are no text segments inside the area
+     * @hide
+     */
+    @Nullable
+    public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentIterator segmentIterator) {
+        // Find the first line whose vertical center is below the top of the area.
+        int startLine = getLineForVertical((int) area.top);
+        int startLineTop = getLineTop(startLine);
+        int startLineBottom = getLineBottomWithoutSpacing(startLine);
+        if (area.top > (startLineTop + startLineBottom) / 2f) {
+            startLine++;
+            if (startLine >= getLineCount()) {
+                // The top of the area is below the vertical center of the last line, so the area
+                // does not contain any text.
+                return null;
+            }
+        }
+
+        // Find the last line whose vertical center is above the bottom of the area.
+        int endLine = getLineForVertical((int) area.bottom);
+        int endLineTop = getLineTop(endLine);
+        int endLineBottom = getLineBottomWithoutSpacing(endLine);
+        if (area.bottom < (endLineTop + endLineBottom) / 2f) {
+            endLine--;
+        }
+        if (endLine < startLine) {
+            // There are no lines with vertical centers between the top and bottom of the area, so
+            // the area does not contain any text.
+            return null;
+        }
+
+        int start = getStartOrEndOffsetForHorizontalInterval(
+                startLine, area.left, area.right, segmentIterator, /* getStart= */ true);
+        // If the area does not contain any text on this line, keep trying subsequent lines until
+        // the end line is reached.
+        while (start == -1 && startLine < endLine) {
+            startLine++;
+            start = getStartOrEndOffsetForHorizontalInterval(
+                    startLine, area.left, area.right, segmentIterator, /* getStart= */ true);
+        }
+        if (start == -1) {
+            // All lines were checked, the area does not contain any text.
+            return null;
+        }
+
+        int end = getStartOrEndOffsetForHorizontalInterval(
+                endLine, area.left, area.right, segmentIterator, /* getStart= */ false);
+        // If the area does not contain any text on this line, keep trying previous lines until
+        // the start line is reached.
+        while (end == -1 && startLine < endLine) {
+            endLine--;
+            end = getStartOrEndOffsetForHorizontalInterval(
+                    endLine, area.left, area.right, segmentIterator, /* getStart= */ false);
+        }
+        if (end == -1) {
+            // All lines were checked, the area does not contain any text.
+            return null;
+        }
+
+        // If a text segment spans multiple lines or multiple directional runs (e.g. a hyphenated
+        // word), then getStartOrEndOffsetForHorizontalInterval() can return an offset in the middle
+        // of a text segment. Adjust the range to include the rest of any partial text segments. If
+        // start is already the start boundary of a text segment, then this is a no-op.
+        start = segmentIterator.previousStartBoundary(start + 1);
+        end = segmentIterator.nextEndBoundary(end - 1);
+
+        return new int[] {start, end};
+    }
+
+    /**
+     * Finds the start character offset of the first text segment inside a horizontal interval
+     * within a line, or the end character offset of the last text segment inside the horizontal
+     * interval.
+     *
+     * @param line index of the line to search
+     * @param left left bound of the horizontal interval
+     * @param right right bound of the horizontal interval
+     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
+     *     segment
+     * @param getStart true to find the start of the first text segment inside the horizontal
+     *     interval, false to find the end of the last text segment
+     * @return the start character offset of the first text segment inside the horizontal interval,
+     *     or the end character offset of the last text segment inside the horizontal interval.
+     */
+    private int getStartOrEndOffsetForHorizontalInterval(
+            @IntRange(from = 0) int line, float left, float right,
+            @NonNull SegmentIterator segmentIterator, boolean getStart) {
+        int lineStartOffset = getLineStart(line);
+        int lineEndOffset = getLineEnd(line);
+        if (lineStartOffset == lineEndOffset) {
+            return -1;
+        }
+
+        float[] horizontalBounds = new float[2 * (lineEndOffset - lineStartOffset)];
+        fillHorizontalBoundsForLine(line, horizontalBounds);
+
+        int lineStartPos = getLineStartPos(line, getParagraphLeft(line), getParagraphRight(line));
+
+        // Loop through the runs forwards or backwards depending on getStart value.
+        Layout.Directions directions = getLineDirections(line);
+        int runIndex = getStart ? 0 : directions.getRunCount() - 1;
+        while ((getStart && runIndex < directions.getRunCount()) || (!getStart && runIndex >= 0)) {
+            // runStartOffset and runEndOffset are offset indices within the line.
+            int runStartOffset = directions.getRunStart(runIndex);
+            int runEndOffset = Math.min(
+                    runStartOffset + directions.getRunLength(runIndex),
+                    lineEndOffset - lineStartOffset);
+            boolean isRtl = directions.isRunRtl(runIndex);
+            float runLeft = lineStartPos
+                    + (isRtl
+                            ? horizontalBounds[2 * (runEndOffset - 1)]
+                            : horizontalBounds[2 * runStartOffset]);
+            float runRight = lineStartPos
+                    + (isRtl
+                            ? horizontalBounds[2 * runStartOffset + 1]
+                            : horizontalBounds[2 * (runEndOffset - 1) + 1]);
+
+            int result =
+                    getStart
+                            ? getStartOffsetForHorizontalIntervalWithinRun(
+                            left, right, lineStartOffset, lineStartPos, horizontalBounds,
+                            runStartOffset, runEndOffset, runLeft, runRight, isRtl,
+                            segmentIterator)
+                            : getEndOffsetForHorizontalIntervalWithinRun(
+                                    left, right, lineStartOffset, lineStartPos, horizontalBounds,
+                                    runStartOffset, runEndOffset, runLeft, runRight, isRtl,
+                                    segmentIterator);
+            if (result >= 0) {
+                return result;
+            }
+
+            runIndex += getStart ? 1 : -1;
+        }
+        return -1;
+    }
+
+    /**
+     * Finds the start character offset of the first text segment inside a horizontal interval
+     * within a directional run.
+     *
+     * @param left left bound of the horizontal interval
+     * @param right right bound of the horizontal interval
+     * @param lineStartOffset start character offset of the line containing this run
+     * @param lineStartPos start position of the line containing this run
+     * @param horizontalBounds array containing the signed horizontal bounds of the characters in
+     *     the line. The left and right bounds of the character at offset i are stored at index (2 *
+     *     i) and index (2 * i + 1). Bounds are relative to {@code lineStartPos}.
+     * @param runStartOffset start offset of the run relative to {@code lineStartOffset}
+     * @param runEndOffset end offset of the run relative to {@code lineStartOffset}
+     * @param runLeft left bound of the run
+     * @param runRight right bound of the run
+     * @param isRtl whether the run is right-to-left
+     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
+     *     segment
+     * @return the start character offset of the first text segment inside the horizontal interval
+     */
+    private static int getStartOffsetForHorizontalIntervalWithinRun(
+            float left, float right,
+            @IntRange(from = 0) int lineStartOffset,
+            @IntRange(from = 0) int lineStartPos,
+            @NonNull float[] horizontalBounds,
+            @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset,
+            float runLeft, float runRight,
+            boolean isRtl,
+            @NonNull SegmentIterator segmentIterator) {
+        if (runRight < left || runLeft > right) {
+            // The run does not overlap the interval.
+            return -1;
+        }
+
+        // Find the first character in the run whose bounds overlap with the interval.
+        // firstCharOffset is an offset index within the line.
+        int firstCharOffset;
+        if ((!isRtl && left <= runLeft) || (isRtl && right >= runRight)) {
+            firstCharOffset = runStartOffset;
+        } else {
+            int low = runStartOffset;
+            int high = runEndOffset;
+            int guess;
+            while (high - low > 1) {
+                guess = (high + low) / 2;
+                // Left edge of the character at guess
+                float pos = lineStartPos + horizontalBounds[2 * guess];
+                if ((!isRtl && pos > left) || (isRtl && pos < right)) {
+                    high = guess;
+                } else {
+                    low = guess;
+                }
+            }
+            // The interval bound is between the left edge of the character at low and the left edge
+            // of the character at high. For LTR text, this is within the character at low. For RTL
+            // text, this is within the character at high.
+            firstCharOffset = isRtl ? high : low;
+        }
+
+        // Find the first text segment containing this character (or, if no text segment contains
+        // this character, the first text segment after this character). All previous text segments
+        // in this run are to the left (for LTR) of the interval.
+        segmentIterator.setRunLimits(
+                lineStartOffset + runStartOffset, lineStartOffset + runEndOffset);
+        int segmentEndOffset =
+                segmentIterator.nextEndBoundaryOrRunEnd(lineStartOffset + firstCharOffset);
+        if (segmentEndOffset == SegmentIterator.DONE) {
+            // There are no text segments containing or after firstCharOffset, so no text segments
+            // in this run overlap the interval.
+            return -1;
+        }
+        int segmentStartOffset = segmentIterator.previousStartBoundaryOrRunStart(segmentEndOffset);
+        float segmentCenter = lineStartPos
+                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
+                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
+        if ((!isRtl && segmentCenter > right) || (isRtl && segmentCenter < left)) {
+            // The entire interval is to the left (for LTR) of the text segment's center. So the
+            // interval does not contain any text segments within this run.
+            return -1;
+        }
+        if ((!isRtl && segmentCenter >= left) || (isRtl && segmentCenter <= right)) {
+            // The center is within the interval, so return the start offset of this text segment.
+            return segmentStartOffset;
+        }
+
+        // If first text segment's center is not within the interval, try the next text segment.
+        segmentStartOffset = segmentIterator.nextStartBoundaryWithinRunLimits(segmentStartOffset);
+        if (segmentStartOffset == SegmentIterator.DONE
+                || segmentStartOffset == lineStartOffset + runEndOffset) {
+            // No more text segments within this run.
+            return -1;
+        }
+        segmentEndOffset = segmentIterator.nextEndBoundaryOrRunEnd(segmentStartOffset);
+        segmentCenter = lineStartPos
+                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
+                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
+        // We already know that segmentCenter >= left (for LTR) since the previous word contains the
+        // left point.
+        if ((!isRtl && segmentCenter <= right) || (isRtl && segmentCenter >= left)) {
+            return segmentStartOffset;
+        }
+
+        // If the second text segment is also not within the interval, then this means that the
+        // interval is between the centers of the first and second text segments, so it does not
+        // contain any text segments on this line.
+        return -1;
+    }
+
+    /**
+     * Finds the end character offset of the last text segment inside a horizontal interval within a
+     * directional run.
+     *
+     * @param left left bound of the horizontal interval
+     * @param right right bound of the horizontal interval
+     * @param lineStartOffset start character offset of the line containing this run
+     * @param lineStartPos start position of the line containing this run
+     * @param horizontalBounds array containing the signed horizontal bounds of the characters in
+     *     the line. The left and right bounds of the character at offset i are stored at index (2 *
+     *     i) and index (2 * i + 1). Bounds are relative to {@code lineStartPos}.
+     * @param runStartOffset start offset of the run relative to {@code lineStartOffset}
+     * @param runEndOffset end offset of the run relative to {@code lineStartOffset}
+     * @param runLeft left bound of the run
+     * @param runRight right bound of the run
+     * @param isRtl whether the run is right-to-left
+     * @param segmentIterator iterator for determining the ranges of text to be considered as a text
+     *     segment
+     * @return the end character offset of the last text segment inside the horizontal interval
+     */
+    private static int getEndOffsetForHorizontalIntervalWithinRun(
+            float left, float right,
+            @IntRange(from = 0) int lineStartOffset,
+            @IntRange(from = 0) int lineStartPos,
+            @NonNull float[] horizontalBounds,
+            @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset,
+            float runLeft, float runRight,
+            boolean isRtl,
+            @NonNull SegmentIterator segmentIterator) {
+        if (runRight < left || runLeft > right) {
+            // The run does not overlap the interval.
+            return -1;
+        }
+
+        // Find the last character in the run whose bounds overlap with the interval.
+        // firstCharOffset is an offset index within the line.
+        int lastCharOffset;
+        if ((!isRtl && right >= runRight) || (isRtl && left <= runLeft)) {
+            lastCharOffset = runEndOffset - 1;
+        } else {
+            int low = runStartOffset;
+            int high = runEndOffset;
+            int guess;
+            while (high - low > 1) {
+                guess = (high + low) / 2;
+                // Left edge of the character at guess
+                float pos = lineStartPos + horizontalBounds[2 * guess];
+                if ((!isRtl && pos > right) || (isRtl && pos < left)) {
+                    high = guess;
+                } else {
+                    low = guess;
+                }
+            }
+            // The interval bound is between the left edge of the character at low and the left edge
+            // of the character at high. For LTR text, this is within the character at low. For RTL
+            // text, this is within the character at high.
+            lastCharOffset = isRtl ? high : low;
+        }
+
+        // Find the last text segment containing this character (or, if no text segment
+        // contains this character, the first text segment before this character). All
+        // following text segments in this run are to the right (for LTR) of the interval.
+        segmentIterator.setRunLimits(
+                lineStartOffset + runStartOffset, lineStartOffset + runEndOffset);
+        // + 1 to allow segmentStartOffset = lineStartOffset + lastCharOffset
+        int segmentStartOffset =
+                segmentIterator.previousStartBoundaryOrRunStart(
+                        lineStartOffset + lastCharOffset + 1);
+        if (segmentStartOffset == SegmentIterator.DONE) {
+            // There are no text segments containing or before lastCharOffset, so no text segments
+            // in this run overlap the interval.
+            return -1;
+        }
+        int segmentEndOffset = segmentIterator.nextEndBoundaryOrRunEnd(segmentStartOffset);
+        float segmentCenter = lineStartPos
+                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
+                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
+        if ((!isRtl && segmentCenter < left) || (isRtl && segmentCenter > right)) {
+            // The entire interval is to the right (for LTR) of the text segment's center. So the
+            // interval does not contain any text segments within this run.
+            return -1;
+        }
+        if ((!isRtl && segmentCenter <= right) || (isRtl && segmentCenter >= left)) {
+            // The center is within the interval, so return the end offset of this text segment.
+            return segmentEndOffset;
+        }
+
+        // If first text segment's center is not within the interval, try the next text segment.
+        segmentEndOffset = segmentIterator.previousEndBoundaryWithinRunLimits(segmentEndOffset);
+        if (segmentEndOffset == SegmentIterator.DONE
+                || segmentEndOffset == lineStartOffset + runStartOffset) {
+            // No more text segments within this run.
+            return -1;
+        }
+        segmentStartOffset = segmentIterator.previousStartBoundaryOrRunStart(segmentEndOffset);
+        segmentCenter = lineStartPos
+                + (horizontalBounds[2 * (segmentStartOffset - lineStartOffset)]
+                        + horizontalBounds[2 * (segmentEndOffset - lineStartOffset) - 1]) / 2;
+        // We already know that segmentCenter <= right (for LTR) since the following word
+        // contains the right point.
+        if ((!isRtl && segmentCenter >= left) || (isRtl && segmentCenter <= right)) {
+            return segmentEndOffset;
+        }
+
+        // If the second text segment is also not within the interval, then this means that the
+        // interval is between the centers of the first and second text segments, so it does not
+        // contain any text segments on this line.
+        return -1;
+    }
+
+    /**
      * Return the text offset after the last character on the specified line.
      */
     public final int getLineEnd(int line) {
diff --git a/core/java/android/text/SegmentIterator.java b/core/java/android/text/SegmentIterator.java
new file mode 100644
index 0000000..00522e5
--- /dev/null
+++ b/core/java/android/text/SegmentIterator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.IntRange;
+
+/**
+ * Finds text segment boundaries within text. Subclasses can implement different types of text
+ * segments. Grapheme clusters and words are examples of possible text segments.
+ *
+ * <p>Granular units may not overlap, so every character belongs to at most one text segment. A
+ * character may belong to no text segments.
+ *
+ * <p>For example, a word level text segment iterator may subdivide the text "Hello, World!" into
+ * four text segments: "Hello", ",", "World", "!". The space character does not belong to any text
+ * segments.
+ *
+ * @hide
+ */
+public abstract class SegmentIterator {
+    public static final int DONE = -1;
+
+    private int mRunStartOffset;
+    private int mRunEndOffset;
+
+    /**
+     * Returns the character offset of the previous text segment start boundary before the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int previousStartBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the previous text segment end boundary before the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int previousEndBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the next text segment start boundary after the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int nextStartBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Returns the character offset of the next text segment end boundary after the specified
+     * character offset, or {@code DONE} if there are none.
+     */
+    public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * Sets the start and end of a run which can be used to constrain the scope of the iterator's
+     * search.
+     *
+     * @hide
+     */
+    void setRunLimits(
+            @IntRange(from = 0) int runStartOffset, @IntRange(from = 0) int runEndOffset) {
+        mRunStartOffset = runStartOffset;
+        mRunEndOffset = runEndOffset;
+    }
+
+    /** @hide */
+    int previousStartBoundaryOrRunStart(@IntRange(from = 0) int offset) {
+        int start = previousStartBoundary(offset);
+        if (start == DONE) {
+            return DONE;
+        }
+        return Math.max(start, mRunStartOffset);
+    }
+
+    /** @hide */
+    int previousEndBoundaryWithinRunLimits(@IntRange(from = 0) int offset) {
+        int end = previousEndBoundary(offset);
+        if (end <= mRunStartOffset) {
+            return DONE;
+        }
+        return end;
+    }
+
+    /** @hide */
+    int nextStartBoundaryWithinRunLimits(@IntRange(from = 0) int offset) {
+        int start = nextStartBoundary(offset);
+        if (start >= mRunEndOffset) {
+            return DONE;
+        }
+        return start;
+    }
+
+    /** @hide */
+    int nextEndBoundaryOrRunEnd(@IntRange(from = 0) int offset) {
+        int end = nextEndBoundary(offset);
+        if (end == DONE) {
+            return DONE;
+        }
+        return Math.min(end, mRunEndOffset);
+    }
+}
diff --git a/core/java/android/text/WordSegmentIterator.java b/core/java/android/text/WordSegmentIterator.java
new file mode 100644
index 0000000..1115319
--- /dev/null
+++ b/core/java/android/text/WordSegmentIterator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.icu.text.BreakIterator;
+import android.text.method.WordIterator;
+
+/**
+ * Implementation of {@code SegmentIterator} using words as the text segment. Word boundaries are
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
+ * any text segments.
+ *
+ * @hide
+ */
+public class WordSegmentIterator extends SegmentIterator {
+    private final CharSequence mText;
+    private final WordIterator mWordIterator;
+
+    public WordSegmentIterator(@NonNull CharSequence text, @NonNull WordIterator wordIterator) {
+        mText = text;
+        mWordIterator = wordIterator;
+    }
+
+    @Override
+    public int previousStartBoundary(@IntRange(from = 0) int offset) {
+        int boundary = offset;
+        do {
+            boundary = mWordIterator.prevBoundary(boundary);
+            if (boundary == BreakIterator.DONE) {
+                return DONE;
+            }
+        } while (Character.isWhitespace(mText.charAt(boundary)));
+        return boundary;
+    }
+
+    @Override
+    public int previousEndBoundary(@IntRange(from = 0) int offset) {
+        int boundary = offset;
+        do {
+            boundary = mWordIterator.prevBoundary(boundary);
+            if (boundary == BreakIterator.DONE || boundary == 0) {
+                return DONE;
+            }
+        } while (Character.isWhitespace(mText.charAt(boundary - 1)));
+        return boundary;
+    }
+
+    @Override
+    public int nextStartBoundary(@IntRange(from = 0) int offset) {
+        int boundary = offset;
+        do {
+            boundary = mWordIterator.nextBoundary(boundary);
+            if (boundary == BreakIterator.DONE || boundary == mText.length()) {
+                return DONE;
+            }
+        } while (Character.isWhitespace(mText.charAt(boundary)));
+        return boundary;
+    }
+
+    @Override
+    public int nextEndBoundary(@IntRange(from = 0) int offset) {
+        int boundary = offset;
+        do {
+            boundary = mWordIterator.nextBoundary(boundary);
+            if (boundary == BreakIterator.DONE) {
+                return DONE;
+            }
+        } while (Character.isWhitespace(mText.charAt(boundary - 1)));
+        return boundary;
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index bca100a..20f01ae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -68,6 +68,12 @@
     public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
             "settings_app_locale_opt_in_enabled";
 
+    /**
+     * Launch the Volume panel in SystemUI.
+     * @hide
+     */
+    public static final String SETTINGS_VOLUME_PANEL_IN_SYSTEMUI =
+            "settings_volume_panel_in_systemui";
 
     /** @hide */
     public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
@@ -110,6 +116,12 @@
      */
     public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
 
+    /**
+     * Enable the new pages which is implemented with SPA.
+     * @hide
+     */
+    public static final String SETTINGS_ENABLE_SPA = "settings_enable_spa";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -134,6 +146,7 @@
         DEFAULT_FLAGS.put("settings_search_always_expand", "true");
         DEFAULT_FLAGS.put(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE, "false");
         DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
+        DEFAULT_FLAGS.put(SETTINGS_VOLUME_PANEL_IN_SYSTEMUI, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
@@ -141,6 +154,7 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
         DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0bed342..ea00125 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1509,6 +1509,13 @@
      */
     public static final int TOOL_TYPE_PALM = 5;
 
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef(prefix = { "TOOL_TYPE_" }, value = {
+            TOOL_TYPE_UNKNOWN, TOOL_TYPE_FINGER, TOOL_TYPE_STYLUS, TOOL_TYPE_MOUSE,
+            TOOL_TYPE_ERASER, TOOL_TYPE_PALM})
+    public @interface ToolType {};
+
     // NOTE: If you add a new tool type here you must also add it to:
     //  native/include/android/input.h
 
@@ -2422,7 +2429,7 @@
      * @see #TOOL_TYPE_STYLUS
      * @see #TOOL_TYPE_MOUSE
      */
-    public final int getToolType(int pointerIndex) {
+    public @ToolType int getToolType(int pointerIndex) {
         return nativeGetToolType(mNativePtr, pointerIndex);
     }
 
@@ -3868,7 +3875,7 @@
      * @return The symbolic name of the specified tool type.
      * @hide
      */
-    public static String toolTypeToString(int toolType) {
+    public static String toolTypeToString(@ToolType int toolType) {
         String symbolicName = TOOL_TYPE_SYMBOLIC_NAMES.get(toolType);
         return symbolicName != null ? symbolicName : Integer.toString(toolType);
     }
@@ -4361,7 +4368,7 @@
          *
          * @see MotionEvent#getToolType(int)
          */
-        public int toolType;
+        public @ToolType int toolType;
 
         /**
          * Resets the pointer properties to their initial values.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e8179cf..9620b09 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9575,7 +9575,7 @@
 
     /**
      * Return the connection ID for the {@link AccessibilityInteractionController} of this instance.
-     * @see AccessibilityNodeInfo#makeQueryableFromAppProcess(View)
+     * @see AccessibilityNodeInfo#enableQueryFromAppProcess(View)
      */
     public int getDirectAccessibilityConnectionId() {
         return mAccessibilityInteractionConnectionManager.ensureDirectConnection();
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6ae59bf..35ec408 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -84,7 +84,7 @@
  * <p>
  * Once an accessibility node info is delivered to an accessibility service it is
  * made immutable and calling a state mutation method generates an error. See
- * {@link #makeQueryableFromAppProcess(View)} if you would like to inspect the
+ * {@link #enableQueryFromAppProcess(View)} if you would like to inspect the
  * node tree from the app process for testing or debugging tools.
  * </p>
  * <p>
@@ -1175,7 +1175,7 @@
      * @return The child node.
      *
      * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
-     *                               calling {@link #makeQueryableFromAppProcess(View)}.
+     *                               calling {@link #enableQueryFromAppProcess(View)}.
      */
     public AccessibilityNodeInfo getChild(int index) {
         return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID);
@@ -1190,7 +1190,7 @@
      * @return The child node.
      *
      * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
-     *                               calling {@link #makeQueryableFromAppProcess(View)}.
+     *                               calling {@link #enableQueryFromAppProcess(View)}.
      *
      * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
      */
@@ -1914,7 +1914,7 @@
      * @return The parent.
      *
      * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
-     *                               calling {@link #makeQueryableFromAppProcess(View)}.
+     *                               calling {@link #enableQueryFromAppProcess(View)}.
      */
     public AccessibilityNodeInfo getParent() {
         enforceSealed();
@@ -1943,7 +1943,7 @@
      * @return The parent.
      *
      * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
-     *                               calling {@link #makeQueryableFromAppProcess(View)}.
+     *                               calling {@link #enableQueryFromAppProcess(View)}.
      *
      * @see #FLAG_PREFETCH_ANCESTORS
      * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
@@ -3669,29 +3669,39 @@
      * {@link AccessibilityNodeInfo} tree and perform accessibility actions on nodes.
      *
      * <p>
-     * This is intended for short-lived inspections from testing or debugging tools in the app
-     * process. After calling this method, all nodes linked to this node (children, ancestors, etc.)
-     * are also queryable. Operations on this node tree will only succeed as long as the associated
-     * view hierarchy remains attached to a window.
-     * </p>
-     *
-     * <p>
-     * Calling this method more than once on the same node is a no-op; if you wish to inspect a
-     * different view hierarchy then create a new node from any view in that hierarchy and call this
-     * method on that node.
-     * </p>
-     *
-     * <p>
      * Testing or debugging tools should create this {@link AccessibilityNodeInfo} node using
      * {@link View#createAccessibilityNodeInfo()} or {@link AccessibilityNodeProvider} and call this
      * method, then navigate and interact with the node tree by calling methods on the node.
+     * Calling this method more than once on the same node is a no-op. After calling this method,
+     * all nodes linked to this node (children, ancestors, etc.) are also queryable.
+     * </p>
+     *
+     * <p>
+     * Here "query" refers to the following node operations:
+     * <li>check properties of this node (example: {@link #isScrollable()})</li>
+     * <li>find and query children (example: {@link #getChild(int)})</li>
+     * <li>find and query the parent (example: {@link #getParent()})</li>
+     * <li>find focus (examples: {@link #findFocus(int)}, {@link #focusSearch(int)})</li>
+     * <li>find and query other nodes (example: {@link #findAccessibilityNodeInfosByText(String)},
+     * {@link #findAccessibilityNodeInfosByViewId(String)})</li>
+     * <li>perform actions (example: {@link #performAction(int)})</li>
+     * </p>
+     *
+     * <p>
+     * This is intended for short-lived inspections from testing or debugging tools in the app
+     * process, as operations on this node tree will only succeed as long as the associated
+     * view hierarchy remains attached to a window. Since {@link AccessibilityNodeInfo} objects can
+     * quickly become out of sync with their corresponding {@link View} objects there is
+     * intentionally no "disable" method: if you wish to inspect a changed or different view
+     * hierarchy then create a new node from any view in that hierarchy and call this method on that
+     * node.
      * </p>
      *
      * @param view The view that generated this node, or any view in the same view-root hierarchy.
      * @throws IllegalStateException If called from an {@link AccessibilityService}, or if provided
      *                               a {@link View} that is not attached to a window.
      */
-    public void makeQueryableFromAppProcess(@NonNull View view) {
+    public void enableQueryFromAppProcess(@NonNull View view) {
         enforceNotSealed();
         if (mConnectionId != UNDEFINED_CONNECTION_ID) {
             return;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 5515548..36b0334 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -42,6 +42,7 @@
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
 import android.view.MotionEvent;
+import android.view.MotionEvent.ToolType;
 import android.view.View;
 import android.view.autofill.AutofillId;
 
@@ -639,8 +640,7 @@
      * Initial {@link MotionEvent#ACTION_UP} tool type {@link MotionEvent#getToolType(int)} that
      * was used to focus this editor.
      */
-    private int mInitialToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
-
+    private @ToolType int mInitialToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
 
     /**
      * Editors may use this method to provide initial input text to IMEs. As the surrounding text
@@ -1022,7 +1022,7 @@
      * @see InputMethodService#onUpdateEditorToolType(int)
      * @return toolType {@link MotionEvent#getToolType(int)}.
      */
-    public int getInitialToolType() {
+    public @ToolType int getInitialToolType() {
         return mInitialToolType;
     }
 
@@ -1034,7 +1034,7 @@
      * @see MotionEvent#getToolType(int)
      * @see InputMethodService#onUpdateEditorToolType(int)
      */
-    public void setInitialToolType(int toolType) {
+    public void setInitialToolType(@ToolType int toolType) {
         mInitialToolType = toolType;
     }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 6825f03..f93b8b7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,8 +226,6 @@
     final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
     boolean mAllowUndo = true;
 
-    private int mLastInputSource = InputDevice.SOURCE_UNKNOWN;
-
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     // Cursor Controllers.
@@ -1735,8 +1733,6 @@
     public void onTouchEvent(MotionEvent event) {
         final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
 
-        mLastInputSource = event.getSource();
-
         mLastButtonState = event.getButtonState();
         if (filterOutEvent) {
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1789,7 +1785,7 @@
     }
 
     private void showFloatingToolbar() {
-        if (mTextActionMode != null && showUIForTouchScreen()) {
+        if (mTextActionMode != null && mTextView.showUIForTouchScreen()) {
             // Delay "show" so it doesn't interfere with click confirmations
             // or double-clicks that could "dismiss" the floating toolbar.
             int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1870,7 +1866,7 @@
                     ? getSelectionController() : getInsertionController();
             if (cursorController != null && !cursorController.isActive()
                     && !cursorController.isCursorBeingModified()
-                    && showUIForTouchScreen()) {
+                    && mTextView.showUIForTouchScreen()) {
                 cursorController.show();
             }
         }
@@ -2521,7 +2517,7 @@
             return false;
         }
 
-        if (!showUIForTouchScreen()) {
+        if (!mTextView.showUIForTouchScreen()) {
             return false;
         }
 
@@ -2677,7 +2673,7 @@
                     mTextView.postDelayed(mShowSuggestionRunnable,
                             ViewConfiguration.getDoubleTapTimeout());
                 } else if (hasInsertionController()) {
-                    if (shouldInsertCursor && showUIForTouchScreen()) {
+                    if (shouldInsertCursor && mTextView.showUIForTouchScreen()) {
                         getInsertionController().show();
                     } else {
                         getInsertionController().hide();
@@ -5408,7 +5404,7 @@
             final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
                     && !tooLargeTextForMagnifier()
                     && obtainMagnifierShowCoordinates(event, showPosInView)
-                    && showUIForTouchScreen();
+                    && mTextView.showUIForTouchScreen();
             if (shouldShow) {
                 // Make the cursor visible and stop blinking.
                 mRenderCursorRegardlessTiming = true;
@@ -6354,16 +6350,6 @@
         }
     }
 
-    /**
-     * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
-     *
-     * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
-     */
-    public boolean showUIForTouchScreen() {
-        return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
-                == InputDevice.SOURCE_TOUCHSCREEN;
-    }
-
     /** Controller for the insertion cursor. */
     @VisibleForTesting
     public class InsertionPointCursorController implements CursorController {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 4ccd77b..be6b08f 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,7 @@
             final SelectionModifierCursorController controller = mEditor.getSelectionController();
             if (controller != null
                     && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
-                if (mEditor.showUIForTouchScreen()) {
+                if (mTextView.showUIForTouchScreen()) {
                     controller.show();
                 } else {
                     controller.hide();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6a572c5..f53dd0c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -72,6 +72,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Path;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -98,12 +99,14 @@
 import android.text.DynamicLayout;
 import android.text.Editable;
 import android.text.GetChars;
+import android.text.GraphemeClusterSegmentIterator;
 import android.text.GraphicsOperations;
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
 import android.text.ParcelableSpan;
 import android.text.PrecomputedText;
+import android.text.SegmentIterator;
 import android.text.Selection;
 import android.text.SpanWatcher;
 import android.text.Spannable;
@@ -117,6 +120,7 @@
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.TextWatcher;
+import android.text.WordSegmentIterator;
 import android.text.method.AllCapsTransformationMethod;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.DateKeyListener;
@@ -183,11 +187,15 @@
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
 import android.view.inspector.InspectableProperty.FlagEntry;
@@ -965,6 +973,11 @@
     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
 
     /**
+     * The last input source on this TextView.
+     */
+    private int mLastInputSource = InputDevice.SOURCE_UNKNOWN;
+
+    /**
      * The TextView does not auto-size text (default).
      */
     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
@@ -9283,6 +9296,83 @@
     }
 
     /** @hide */
+    public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
+        int[] range = getRangeForRect(gesture.getSelectionArea(), gesture.getGranularity());
+        if (range == null) {
+            return handleGestureFailure(gesture);
+        }
+        Selection.setSelection(getEditableText(), range[0], range[1]);
+        mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
+        return 0;
+    }
+
+    /** @hide */
+    public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
+        int[] range = getRangeForRect(gesture.getDeletionArea(), gesture.getGranularity());
+        if (range == null) {
+            return handleGestureFailure(gesture);
+        }
+        getEditableText().delete(range[0], range[1]);
+        Selection.setSelection(getEditableText(), range[0]);
+        // TODO: Delete extra spaces.
+        return 0;
+    }
+
+    /** @hide */
+    public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
+        PointF point = gesture.getInsertionPoint();
+        // The coordinates provided are screen coordinates - transform to content coordinates.
+        int[] screenToViewport = getLocationOnScreen();
+        point.offset(
+                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
+                -(screenToViewport[1] + viewportToContentVerticalOffset()));
+
+        int line = mLayout.getLineForVertical((int) point.y);
+        if (point.y < mLayout.getLineTop(line)
+                || point.y > mLayout.getLineBottomWithoutSpacing(line)) {
+            return handleGestureFailure(gesture);
+        }
+        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
+            return handleGestureFailure(gesture);
+        }
+        int offset = mLayout.getOffsetForHorizontal(line, point.x);
+        String textToInsert = gesture.getTextToInsert();
+        getEditableText().insert(offset, textToInsert);
+        Selection.setSelection(getEditableText(), offset + textToInsert.length());
+        // TODO: Insert extra spaces if necessary.
+        return 0;
+    }
+
+    private int handleGestureFailure(HandwritingGesture gesture) {
+        if (!TextUtils.isEmpty(gesture.getFallbackText())) {
+            getEditableText()
+                    .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
+        }
+        return 0;
+    }
+
+    @Nullable
+    private int[] getRangeForRect(@NonNull RectF area, int granularity) {
+        // The coordinates provided are screen coordinates - transform to content coordinates.
+        int[] screenToViewport = getLocationOnScreen();
+        area = new RectF(area);
+        area.offset(
+                -(screenToViewport[0] + viewportToContentHorizontalOffset()),
+                -(screenToViewport[1] + viewportToContentVerticalOffset()));
+
+        SegmentIterator segmentIterator;
+        if (granularity == HandwritingGesture.GRANULARITY_WORD) {
+            WordIterator wordIterator = getWordIterator();
+            wordIterator.setCharSequence(mText, 0, mText.length());
+            segmentIterator = new WordSegmentIterator(mText, wordIterator);
+        } else {
+            segmentIterator = new GraphemeClusterSegmentIterator(mText, mTextPaint);
+        }
+
+        return mLayout.getRangeForRect(area, segmentIterator);
+    }
+
+    /** @hide */
     @VisibleForTesting
     @UnsupportedAppUsage
     public void nullLayouts() {
@@ -11480,6 +11570,7 @@
                     MotionEvent.actionToString(event.getActionMasked()),
                     event.getX(), event.getY());
         }
+        mLastInputSource = event.getSource();
         final int action = event.getActionMasked();
         if (mEditor != null) {
             if (!isFromPrimePointer(event, false)) {
@@ -11569,6 +11660,17 @@
     }
 
     /**
+     * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
+     *
+     * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
+     * @hide
+     */
+    public final boolean showUIForTouchScreen() {
+        return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN)
+                == InputDevice.SOURCE_TOUCHSCREEN;
+    }
+
+    /**
      * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI.
      * If autofill suggestions are available when the user clicks on a field that supports filling
      * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ff7ea31..db58704 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -669,7 +669,6 @@
      * TaskFragment.
      * @param fragmentToken client assigned unique token to create TaskFragment with specified in
      *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction requestFocusOnTaskFragment(@NonNull IBinder fragmentToken) {
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index be7501f..9211926 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -22,6 +22,9 @@
 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END;
 import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.Selection;
@@ -31,12 +34,19 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.DumpableInputConnection;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.widget.TextView;
 
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
+
 /**
  * Base class for an editable InputConnection instance. This is created by {@link TextView} or
  * {@link android.widget.EditText}.
@@ -257,6 +267,25 @@
     }
 
     @Override
+    public void performHandwritingGesture(
+            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
+            @Nullable IntConsumer consumer) {
+        int result;
+        if (gesture instanceof SelectGesture) {
+            result = mTextView.performHandwritingSelectGesture((SelectGesture) gesture);
+        } else if (gesture instanceof DeleteGesture) {
+            result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture);
+        } else if (gesture instanceof InsertGesture) {
+            result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture);
+        } else {
+            result = 0;
+        }
+        if (executor != null && consumer != null) {
+            executor.execute(() -> consumer.accept(result));
+        }
+    }
+
+    @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         CharSequence editableText = mTextView.getText();
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b11ea29..9ef7ce38 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -19,6 +19,8 @@
 import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.ImageDecoder;
@@ -109,13 +111,13 @@
                 }
                 break;
             case Icon.TYPE_RESOURCE:
-                if (!(TextUtils.isEmpty(icon.getResPackage())
-                        || context.getPackageName().equals(icon.getResPackage()))) {
-                    // We can't properly resolve icons from other packages here, so fall back.
+                Resources res = resolveResourcesForIcon(context, icon);
+                if (res == null) {
+                    // We couldn't resolve resources properly, fall back to icon loading.
                     return icon.loadDrawable(context);
                 }
 
-                Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
+                Drawable result = resolveImage(res, icon.getResId(), maxWidth, maxHeight);
                 if (result != null) {
                     return tintDrawable(icon, result);
                 }
@@ -159,6 +161,13 @@
     }
 
     @Nullable
+    private static Drawable resolveImage(Resources res, @DrawableRes int resId, int maxWidth,
+            int maxHeight) {
+        final ImageDecoder.Source source = ImageDecoder.createSource(res, resId);
+        return resolveImage(source, maxWidth, maxHeight);
+    }
+
+    @Nullable
     private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
             int maxHeight) {
 
@@ -259,4 +268,52 @@
         }
         return icon.getUri();
     }
+
+    /**
+     * Resolves the correct resources package for a given Icon - it may come from another
+     * package.
+     *
+     * @see Icon#loadDrawableInner(Context)
+     * @hide
+     *
+     * @return resources instance if the operation succeeded, null otherwise
+     */
+    @Nullable
+    @VisibleForTesting
+    public static Resources resolveResourcesForIcon(Context context, Icon icon) {
+        if (icon.getType() != Icon.TYPE_RESOURCE) {
+            return null;
+        }
+
+        // Icons cache resolved resources, use cache if available.
+        Resources res = icon.getResources();
+        if (res != null) {
+            return res;
+        }
+
+        String resPackage = icon.getResPackage();
+        // No package means we try to use current context.
+        if (TextUtils.isEmpty(resPackage) || context.getPackageName().equals(resPackage)) {
+            return context.getResources();
+        }
+
+        if ("android".equals(resPackage)) {
+            return Resources.getSystem();
+        }
+
+        final PackageManager pm = context.getPackageManager();
+        try {
+            ApplicationInfo ai = pm.getApplicationInfo(resPackage,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.GET_SHARED_LIBRARY_FILES);
+            if (ai != null) {
+                return pm.getResourcesForApplication(ai);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, String.format("Unable to resolve package %s for icon %s", resPackage, icon));
+            return null;
+        }
+
+        return null;
+    }
 }
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 7f50204..14699e7 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -95,3 +95,7 @@
 # Battery
 per-file com_android_internal_os_Kernel* = file:/BATTERY_STATS_OWNERS
 per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNERS
+
+# PM
+per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
+
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index be82879..acf4da6 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -17,28 +17,25 @@
 #define LOG_TAG "NativeLibraryHelper"
 //#define LOG_NDEBUG 0
 
-#include "core_jni_helpers.h"
-
-#include <nativehelper/ScopedUtfChars.h>
 #include <androidfw/ZipFileRO.h>
 #include <androidfw/ZipUtils.h>
-#include <utils/Log.h>
-#include <utils/Vector.h>
-
-#include <zlib.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <stdlib.h>
 #include <string.h>
-#include <time.h>
-#include <unistd.h>
-#include <inttypes.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <utils/Log.h>
+#include <zlib.h>
 
 #include <memory>
 
+#include "core_jni_helpers.h"
+
 #define APK_LIB "lib/"
 #define APK_LIB_LEN (sizeof(APK_LIB) - 1)
 
@@ -156,7 +153,7 @@
     size_t* total = (size_t*) arg;
     uint32_t uncompLen;
 
-    if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, NULL, NULL)) {
+    if (!zipFile->getEntryInfo(zipEntry, nullptr, &uncompLen, nullptr, nullptr, nullptr, nullptr)) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
@@ -186,7 +183,7 @@
     uint16_t method;
     off64_t offset;
 
-    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) {
+    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
         ALOGE("Couldn't read zip entry info\n");
         return INSTALL_FAILED_INVALID_APK;
     }
@@ -307,24 +304,24 @@
 class NativeLibrariesIterator {
 private:
     NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie)
-        : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(NULL) {
+          : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(nullptr) {
         fileName[0] = '\0';
     }
 
 public:
     static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) {
-        void* cookie = NULL;
+        void* cookie = nullptr;
         // Do not specify a suffix to find both .so files and gdbserver.
-        if (!zipFile->startIteration(&cookie, APK_LIB, NULL /* suffix */)) {
-            return NULL;
+        if (!zipFile->startIteration(&cookie, APK_LIB, nullptr /* suffix */)) {
+            return nullptr;
         }
 
         return new NativeLibrariesIterator(zipFile, debuggable, cookie);
     }
 
     ZipEntryRO next() {
-        ZipEntryRO next = NULL;
-        while ((next = mZipFile->nextEntry(mCookie)) != NULL) {
+        ZipEntryRO next = nullptr;
+        while ((next = mZipFile->nextEntry(mCookie)) != nullptr) {
             // Make sure this entry has a filename.
             if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
                 continue;
@@ -338,7 +335,7 @@
             }
 
             const char* lastSlash = strrchr(fileName, '/');
-            ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName);
+            ALOG_ASSERT(lastSlash != nullptr, "last slash was null somehow for %s\n", fileName);
 
             // Skip directories.
             if (*(lastSlash + 1) == 0) {
@@ -389,24 +386,23 @@
 iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
                        jboolean debuggable, iterFunc callFunc, void* callArg) {
     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
-    if (zipFile == NULL) {
+    if (zipFile == nullptr) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
     std::unique_ptr<NativeLibrariesIterator> it(
             NativeLibrariesIterator::create(zipFile, debuggable));
-    if (it.get() == NULL) {
+    if (it.get() == nullptr) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
     const ScopedUtfChars cpuAbi(env, javaCpuAbi);
-    if (cpuAbi.c_str() == NULL) {
-        // This would've thrown, so this return code isn't observable by
-        // Java.
+    if (cpuAbi.c_str() == nullptr) {
+        // This would've thrown, so this return code isn't observable by Java.
         return INSTALL_FAILED_INVALID_APK;
     }
-    ZipEntryRO entry = NULL;
-    while ((entry = it->next()) != NULL) {
+    ZipEntryRO entry = nullptr;
+    while ((entry = it->next()) != nullptr) {
         const char* fileName = it->currentEntry();
         const char* lastSlash = it->lastSlash();
 
@@ -427,31 +423,30 @@
     return INSTALL_SUCCEEDED;
 }
 
-
-static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray,
-        jboolean debuggable) {
-    const int numAbis = env->GetArrayLength(supportedAbisArray);
-    Vector<ScopedUtfChars*> supportedAbis;
-
-    for (int i = 0; i < numAbis; ++i) {
-        supportedAbis.add(new ScopedUtfChars(env,
-            (jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
-    }
-
+static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray,
+                            jboolean debuggable) {
     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
-    if (zipFile == NULL) {
+    if (zipFile == nullptr) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
     std::unique_ptr<NativeLibrariesIterator> it(
             NativeLibrariesIterator::create(zipFile, debuggable));
-    if (it.get() == NULL) {
+    if (it.get() == nullptr) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    ZipEntryRO entry = NULL;
+    const int numAbis = env->GetArrayLength(supportedAbisArray);
+
+    std::vector<ScopedUtfChars> supportedAbis;
+    supportedAbis.reserve(numAbis);
+    for (int i = 0; i < numAbis; ++i) {
+        supportedAbis.emplace_back(env, (jstring)env->GetObjectArrayElement(supportedAbisArray, i));
+    }
+
+    ZipEntryRO entry = nullptr;
     int status = NO_NATIVE_LIBRARIES;
-    while ((entry = it->next()) != NULL) {
+    while ((entry = it->next()) != nullptr) {
         // We're currently in the lib/ directory of the APK, so it does have some native
         // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
         // libraries match.
@@ -466,8 +461,8 @@
         const char* abiOffset = fileName + APK_LIB_LEN;
         const size_t abiSize = lastSlash - abiOffset;
         for (int i = 0; i < numAbis; i++) {
-            const ScopedUtfChars* abi = supportedAbis[i];
-            if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
+            const ScopedUtfChars& abi = supportedAbis[i];
+            if (abi.size() == abiSize && !strncmp(abiOffset, abi.c_str(), abiSize)) {
                 // The entry that comes in first (i.e. with a lower index) has the higher priority.
                 if (((i < status) && (status >= 0)) || (status < 0) ) {
                     status = i;
@@ -476,10 +471,6 @@
         }
     }
 
-    for (int i = 0; i < numAbis; ++i) {
-        delete supportedAbis[i];
-    }
-
     return status;
 }
 
@@ -521,19 +512,19 @@
 com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz,
         jlong apkHandle) {
     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
-    void* cookie = NULL;
-    if (!zipFile->startIteration(&cookie, NULL /* prefix */, RS_BITCODE_SUFFIX)) {
+    void* cookie = nullptr;
+    if (!zipFile->startIteration(&cookie, nullptr /* prefix */, RS_BITCODE_SUFFIX)) {
         return APK_SCAN_ERROR;
     }
 
     char fileName[PATH_MAX];
-    ZipEntryRO next = NULL;
-    while ((next = zipFile->nextEntry(cookie)) != NULL) {
+    ZipEntryRO next = nullptr;
+    while ((next = zipFile->nextEntry(cookie)) != nullptr) {
         if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
             continue;
         }
         const char* lastSlash = strrchr(fileName, '/');
-        const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1;
+        const char* baseName = (lastSlash == nullptr) ? fileName : fileName + 1;
         if (isFilenameSafe(baseName)) {
             zipFile->endIteration(cookie);
             return BITCODE_PRESENT;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cd518ce..5b4fc56 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -815,6 +815,10 @@
     <protected-broadcast android:name="android.app.action.PROVISIONING_COMPLETED" />
     <protected-broadcast android:name="android.app.action.LOST_MODE_LOCATION_UPDATE" />
 
+    <!-- Added in U -->
+    <protected-broadcast android:name="android.intent.action.PROFILE_ADDED" />
+    <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6a52ec9..e7cae76 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1565,7 +1565,8 @@
     <bool name="config_enableIdleScreenBrightnessMode">false</bool>
 
     <!-- Array of desired screen brightness in nits corresponding to the lux values
-         in the config_autoBrightnessLevels array. The display brightness is defined as the measured
+         in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
+         config_screenBrightnessMaximumNits, the display brightness is defined as the measured
          brightness of an all-white image.
 
          If this is defined then:
@@ -1586,7 +1587,7 @@
     <array name="config_autoBrightnessDisplayValuesNitsIdle">
     </array>
 
-    <!-- Array of output values for button backlight corresponding to the lux values
+    <!-- Array of output values for button backlight corresponding to the luX values
          in the config_autoBrightnessLevels array.  This array should have size one greater
          than the size of the config_autoBrightnessLevels array.
          The brightness values must be between 0 and 255 and be non-decreasing.
diff --git a/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
new file mode 100644
index 0000000..32fdb5e
--- /dev/null
+++ b/core/tests/coretests/src/android/text/LayoutGetRangeForRectTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.method.WordIterator;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LayoutGetRangeForRectTest {
+
+    private static final int WIDTH = 200;
+    private static final int HEIGHT = 1000;
+    private static final String DEFAULT_TEXT = ""
+            // Line 0 (offset 0 to 18)
+            // - Word 0 (offset 0 to 4) has bounds [0, 40], center 20
+            // - Word 1 (offset 5 to 11) has bounds [50, 110], center 80
+            // - Word 2 (offset 12 to 17) has bounds [120, 170], center 145
+            + "XXXX XXXXXX XXXXX "
+            // Line 1 (offset 18 to 36)
+            // - Word 3 (offset 18 to 23) has bounds [0, 50], center 25
+            // - Word 4 (offset 24 to 26, RTL) has bounds [100, 110], center 105
+            // - Word 5 (offset 27 to 29, RTL) has bounds [80, 90], center 85
+            // - Word 6 start part (offset 30 to 32, RTL) has bounds [60, 70], center 65
+            // - Word 6 end part (offset 32 to 35) has bounds [110, 140], center 125
+            + "XXXXX \u05D1\u05D1 \u05D1\u05D1 \u05D1\u05D1XXX\n"
+            // Line 2 (offset 36 to 38)
+            // - Word 7 start part (offset 36 to 38) has bounds [0, 150], center 75
+            // Line 3 (offset 38 to 40)
+            // - Word 7 middle part (offset 38 to 40) has bounds [0, 150], center 75
+            // Line 4 (offset 40 to 46)
+            // - Word 7 end part (offset 40 to 41) has bounds [0, 100], center 50
+            // - Word 8 (offset 42 to 44) has bounds [110, 130], center 120
+            + "CLCLC XX \n";
+
+    private Layout mLayout;
+    private float[] mLineCenters;
+    private GraphemeClusterSegmentIterator mGraphemeClusterSegmentIterator;
+    private WordSegmentIterator mWordSegmentIterator;
+
+    @Before
+    public void setup() {
+        // The test font includes the following characters:
+        // U+0020 ( ): 10em
+        // U+002E (.): 10em
+        // U+0049 (I): 1em
+        // U+0056 (V): 5em
+        // U+0058 (X): 10em
+        // U+004C (L): 50em
+        // U+0043 (C): 100em
+        // U+005F (_): 0em
+        // U+05D0    : 1em  // HEBREW LETTER ALEF
+        // U+05D1    : 5em  // HEBREW LETTER BET
+        // U+FFFD (invalid surrogate will be replaced to this): 7em
+        // U+10331 (\uD800\uDF31): 10em
+        // Undefined : 0.5em
+        TextPaint textPaint = new TextPaint();
+        textPaint.setTypeface(
+                Typeface.createFromAsset(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(),
+                        "fonts/StaticLayoutLineBreakingTestFont.ttf"));
+        // Make 1 em equal to 1 pixel.
+        textPaint.setTextSize(1.0f);
+
+        mLayout = StaticLayout.Builder.obtain(
+                DEFAULT_TEXT, 0, DEFAULT_TEXT.length(), textPaint, WIDTH).build();
+
+        mLineCenters = new float[mLayout.getLineCount()];
+        for (int i = 0; i < mLayout.getLineCount(); ++i) {
+            mLineCenters[i] = (mLayout.getLineTop(i) + mLayout.getLineBottomWithoutSpacing(i)) / 2f;
+        }
+
+        mGraphemeClusterSegmentIterator =
+                new GraphemeClusterSegmentIterator(DEFAULT_TEXT, textPaint);
+        WordIterator wordIterator = new WordIterator();
+        wordIterator.setCharSequence(DEFAULT_TEXT, 0, DEFAULT_TEXT.length());
+        mWordSegmentIterator = new WordSegmentIterator(DEFAULT_TEXT, wordIterator);
+    }
+
+    @Test
+    public void getRangeForRect_character() {
+        // Character 1 on line 0 has center 15.
+        // Character 2 on line 0 has center 25.
+        RectF area = new RectF(14f, mLineCenters[0] - 1f, 26f, mLineCenters[0] + 1f);
+
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(1, 3).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_partialCharacterButNotCenter() {
+        // Character 0 on line 0 has center 5.
+        // Character 1 on line 0 has center 15.
+        // Character 2 on line 0 has center 25.
+        // Character 3 on line 0 has center 35.
+        RectF area = new RectF(6f, mLineCenters[0] - 1f, 34f, mLineCenters[0] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        // Area partially overlaps characters 0 and 3 but does not contain their centers.
+        assertThat(range).asList().containsExactly(1, 3).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_rtl() {
+        // Character 25 on line 1 has center 102.5.
+        // Character 26 on line 1 has center 95.
+        RectF area = new RectF(94f, mLineCenters[1] - 1f, 103f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(25, 27).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_ltrAndRtl() {
+        // Character 22 on line 1 has center 45.
+        // The end of the RTL run (offset 24 to 32) on line 1 is at 60.
+        RectF area = new RectF(44f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(22, 32).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_rtlAndLtr() {
+        // The start of the RTL run (offset 24 to 32) on line 1 is at 110.
+        // Character 33 on line 1 has center 125.
+        RectF area = new RectF(93f, mLineCenters[1] - 1f, 131f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(24, 34).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_betweenCharacters_shouldFallback() {
+        // Character 1 on line 0 has center 15.
+        // Character 2 on line 0 has center 25.
+        RectF area = new RectF(16f, mLineCenters[0] - 1f, 24f, mLineCenters[0] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_character_betweenLines_shouldFallback() {
+        // Area top is below the center of line 0.
+        // Area bottom is above the center of line 1.
+        RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        // Area partially covers two lines but does not contain the center of any characters.
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_character_multiLine() {
+        // Character 9 on line 0 has center 95.
+        // Character 42 on line 4 has center 115.
+        RectF area = new RectF(93f, 0, 118f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(9, 43).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_multiLine_betweenCharactersOnSomeLines() {
+        // Character 6 on line 0 has center 65.
+        // Character 7 on line 0 has center 75.
+        // Character 30 on line 1 has center 67.5.
+        // Character 36 on line 2 has center 50.
+        // Character 37 on line 2 has center 125.
+        // Character 38 on line 3 has center 50.
+        // Character 39 on line 3 has center 125.
+        // Character 40 on line 4 has center 50.
+        // Character 41 on line 4 has center 105.
+        RectF area = new RectF(66f, 0, 69f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        // Area crosses all lines but does not contain the center of any characters on lines 0, 2,
+        // 3, or 4. So the only included character is character 30 on line 1.
+        assertThat(range).asList().containsExactly(30, 31).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_character_multiLine_betweenCharactersOnAllLines_shouldFallback() {
+        // Character 6 on line 0 has center 65.
+        // Character 7 on line 0 has center 75.
+        // Character 30 on line 1 has center 67.5.
+        // Character 31 on line 1 has center 62.5.
+        // Character 36 on line 2 has center 50.
+        // Character 37 on line 2 has center 125.
+        // Character 38 on line 3 has center 50.
+        // Character 39 on line 3 has center 125.
+        // Character 40 on line 4 has center 50.
+        // Character 41 on line 4 has center 105.
+        RectF area = new RectF(66f, 0, 67f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        // Area crosses all lines but does not contain the center of any characters.
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_character_all() {
+        // Entire area, should include all text.
+        RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mGraphemeClusterSegmentIterator);
+
+        assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length()).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word() {
+        // Word 1 (offset 5 to 11) on line 0 has center 80.
+        RectF area = new RectF(79f, mLineCenters[0] - 1f, 81f, mLineCenters[0] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).asList().containsExactly(5, 11).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_partialWordButNotCenter() {
+        // Word 0 (offset 0 to 4) on line 0 has center 20.
+        // Word 1 (offset 5 to 11) on line 0 has center 80.
+        // Word 2 (offset 12 to 17) on line 0 center 145
+        RectF area = new RectF(21f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Area partially overlaps words 0 and 2 but does not contain their centers, so only word 1
+        // is included. Whitespace between words is not included.
+        assertThat(range).asList().containsExactly(5, 11).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_rtl() {
+        // Word 4 (offset 24 to 26, RTL) on line 1 center 105
+        RectF area = new RectF(88f, mLineCenters[1] - 1f, 119f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).asList().containsExactly(24, 26).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_ltrAndRtl() {
+        // Word 3 (offset 18 to 23) on line 1 has center 25
+        // The end of the RTL run (offset 24 to 32) on line 1 is at 60.
+        RectF area = new RectF(24f, mLineCenters[1] - 1f, 93f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Selects all of word 6, not just the first RTL part.
+        assertThat(range).asList().containsExactly(18, 35).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_rtlAndLtr() {
+        // The start of the RTL run (offset 24 to 32) on line 1 is at 110.
+        // End part of word 6 (offset 32 to 35) on line 1 has center 125.
+        RectF area = new RectF(93f, mLineCenters[1] - 1f, 174f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).asList().containsExactly(24, 35).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_betweenWords_shouldFallback() {
+        // Word 1 on line 0 has center 80.
+        // Word 2 on line 0 has center 145.
+        RectF area = new RectF(81f, mLineCenters[0] - 1f, 144f, mLineCenters[0] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_word_betweenLines_shouldFallback() {
+        // Area top is below the center of line 0.
+        // Area bottom is above the center of line 1.
+        RectF area = new RectF(0f, mLineCenters[0] + 1f, WIDTH, mLineCenters[1] - 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Area partially covers two lines but does not contain the center of any words.
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_word_multiLine() {
+        // Word 1 (offset 5 to 11) on line 0 has center 80.
+        // End part of word 7 (offset 40 to 41) on line 4 has center 50.
+        RectF area = new RectF(42f, 0, 91f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).asList().containsExactly(5, 41).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_multiLine_betweenWordsOnSomeLines() {
+        // Word 1 on line 0 has center 80.
+        // Word 2 on line 0 has center 145.
+        // Word 5 (offset 27 to 29) on line 1 has center 85.
+        // Word 7 on line 2 has center 50.
+        // Word 37 on line 2 has center 125.
+        // Word 38 on line 3 has center 50.
+        // Word 39 on line 3 has center 125.
+        // Word 40 on line 4 has center 50.
+        // Word 41 on line 4 has center 105.
+        RectF area = new RectF(84f, 0, 86f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Area crosses all lines but does not contain the center of any words on lines 0, 2, 3, or
+        // 4. So the only included word is word 5 on line 1.
+        assertThat(range).asList().containsExactly(27, 29).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_multiLine_betweenCharactersOnAllLines_shouldFallback() {
+        // Word 1 on line 0 has center 80.
+        // Word 2 on line 0 has center 145.
+        // Word 4 on line 1 has center 105.
+        // Word 5 on line 1 has center 85.
+        // Word 7 on line 2 has center 50.
+        // Word 37 on line 2 has center 125.
+        // Word 38 on line 3 has center 50.
+        // Word 39 on line 3 has center 125.
+        // Word 40 on line 4 has center 50.
+        // Word 41 on line 4 has center 105.
+        RectF area = new RectF(86f, 0, 89f, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Area crosses all lines but does not contain the center of any words.
+        assertThat(range).isNull();
+    }
+
+    @Test
+    public void getRangeForRect_word_wordSpansMultipleLines_firstPartInsideArea() {
+        // Word 5 (offset 27 to 29) on line 1 has center 85.
+        // First part of word 7 (offset 36 to 38) on line 2 has center 75.
+        RectF area = new RectF(74f, mLineCenters[1] - 1f, 86f, mLineCenters[2] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Selects all of word 7, not just the first part on line 2.
+        assertThat(range).asList().containsExactly(27, 41).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_wordSpansMultipleLines_middlePartInsideArea() {
+        // Middle part of word 7 (offset 38 to 40) on line 2 has center 75.
+        RectF area = new RectF(74f, mLineCenters[3] - 1f, 75f, mLineCenters[3] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Selects all of word 7, not just the middle part on line 3.
+        assertThat(range).asList().containsExactly(36, 41).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_wordSpansMultipleLines_endPartInsideArea() {
+        // End part of word 7 (offset 40 to 41) on line 4 has center 50.
+        // Word 8 (offset 42 to 44) on line 4 has center 120
+        RectF area = new RectF(49f, mLineCenters[4] - 1f, 121f, mLineCenters[4] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Selects all of word 7, not just the middle part on line 3.
+        assertThat(range).asList().containsExactly(36, 44).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_wordSpansMultipleRuns_firstPartInsideArea() {
+        // Word 5 (offset 27 to 29) on line 1 has center 85.
+        // First part of word 6 (offset 30 to 32) on line 1 has center 65.
+        RectF area = new RectF(64f, mLineCenters[1] - 1f, 86f, mLineCenters[1] + 1f);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        // Selects all of word 6, not just the first RTL part.
+        assertThat(range).asList().containsExactly(27, 35).inOrder();
+    }
+
+    @Test
+    public void getRangeForRect_word_all() {
+        // Entire area, should include all text except the last two whitespace characters.
+        RectF area = new RectF(0f, 0f, WIDTH, HEIGHT);
+        int[] range = mLayout.getRangeForRect(area, mWordSegmentIterator);
+
+        assertThat(range).asList().containsExactly(0, DEFAULT_TEXT.length() - 2).inOrder();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index 0cee526..271a20b 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -17,6 +17,8 @@
 package com.android.internal.widget;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
@@ -279,4 +281,49 @@
         // This drawable must not be loaded - if it was, the code ignored the package specification.
         assertThat(d).isNull();
     }
+
+    @Test
+    public void resolveResourcesForIcon_notAResourceIcon_returnsNull() {
+        Icon icon = Icon.createWithContentUri(Uri.parse("some_uri"));
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
+    }
+
+    @Test
+    public void resolveResourcesForIcon_localPackageIcon_returnsPackageResources() {
+        Icon icon = Icon.createWithResource(mContext, R.drawable.test32x24);
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
+                .isSameInstanceAs(mContext.getResources());
+    }
+
+    @Test
+    public void resolveResourcesForIcon_iconWithoutPackageSpecificed_returnsPackageResources() {
+        Icon icon = Icon.createWithResource("", R.drawable.test32x24);
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
+                .isSameInstanceAs(mContext.getResources());
+    }
+
+    @Test
+    public void resolveResourcesForIcon_systemPackageSpecified_returnsSystemPackage() {
+        Icon icon = Icon.createWithResource("android", R.drawable.test32x24);
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isSameInstanceAs(
+                Resources.getSystem());
+    }
+
+    @Test
+    public void resolveResourcesForIcon_differentPackageSpecified_returnsPackageResources() throws
+            PackageManager.NameNotFoundException {
+        String pkg = "com.android.settings";
+        Resources res = mContext.getPackageManager().getResourcesForApplication(pkg);
+        int resId = res.getIdentifier("ic_android", "drawable", pkg);
+        Icon icon = Icon.createWithResource(pkg, resId);
+
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon).getDrawable(resId,
+                mContext.getTheme())).isNotNull();
+    }
+
+    @Test
+    public void resolveResourcesForIcon_invalidPackageSpecified_returnsNull() {
+        Icon icon = Icon.createWithResource("invalid.package", R.drawable.test32x24);
+        assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
+    }
 }
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index f030d80..e0e13f5 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -81,5 +81,6 @@
         <permission name="android.permission.READ_DEVICE_CONFIG" />
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
+        <permission name="android.permission.READ_SEARCH_INDEXABLES" />
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index afcaabe..052b9af 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -33,7 +33,7 @@
  * (based on the paint's Style), or it can be used for clipping or to draw
  * text on a path.
  */
-public class Path {
+public class Path implements Iterable<PathIterator.Segment> {
 
     private static final NativeAllocationRegistry sRegistry =
             NativeAllocationRegistry.createMalloced(
@@ -92,6 +92,17 @@
     }
 
     /**
+     * Returns an iterator over the segments of this path.
+     *
+     * @return the Iterator object
+     */
+    @NonNull
+    @Override
+    public PathIterator iterator() {
+        return new PathIterator(this);
+    }
+
+    /**
      * The logical operations that can be performed when combining two paths.
      *
      * @see #op(Path, android.graphics.Path.Op)
@@ -383,6 +394,49 @@
     }
 
     /**
+     * Add a quadratic bezier from the last point, approaching control point
+     * (x1,y1), and ending at (x2,y2), weighted by <code>weight</code>. If no
+     * moveTo() call has been made for this contour, the first point is
+     * automatically set to (0,0).
+     *
+     * A weight of 1 is equivalent to calling {@link #quadTo(float, float, float, float)}.
+     * A weight of 0 is equivalent to calling {@link #lineTo(float, float)} to
+     * <code>(x1, y1)</code> followed by {@link #lineTo(float, float)} to <code>(x2, y2)</code>.
+     *
+     * @param x1 The x-coordinate of the control point on a conic curve
+     * @param y1 The y-coordinate of the control point on a conic curve
+     * @param x2 The x-coordinate of the end point on a conic curve
+     * @param y2 The y-coordinate of the end point on a conic curve
+     * @param weight The weight of the conic applied to the curve. A value of 1 is equivalent
+     *               to a quadratic with the given control and anchor points and a value of 0 is
+     *               equivalent to a line to the first and another line to the second point.
+     */
+    public void conicTo(float x1, float y1, float x2, float y2, float weight) {
+        nConicTo(mNativePath, x1, y1, x2, y2, weight);
+    }
+
+    /**
+     * Same as conicTo, but the coordinates are considered relative to the last
+     * point on this contour. If there is no previous point, then a moveTo(0,0)
+     * is inserted automatically.
+     *
+     * @param dx1 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the control point of a conic curve
+     * @param dy1 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the control point of a conic curve
+     * @param dx2 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the end point of a conic curve
+     * @param dy2 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the end point of a conic curve
+     * @param weight The weight of the conic applied to the curve. A value of 1 is equivalent
+     *               to a quadratic with the given control and anchor points and a value of 0 is
+     *               equivalent to a line to the first and another line to the second point.
+     */
+    public void rConicTo(float dx1, float dy1, float dx2, float dy2, float weight) {
+        nRConicTo(mNativePath, dx1, dy1, dx2, dy2, weight);
+    }
+
+    /**
      * Add a cubic bezier from the last point, approaching control points
      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
      * made for this contour, the first point is automatically set to (0,0).
@@ -736,6 +790,46 @@
         return nApproximate(mNativePath, acceptableError);
     }
 
+    /**
+     * Returns the generation ID of this path. The generation ID changes
+     * whenever the path is modified. This can be used as an efficient way to
+     * check if a path has changed.
+     *
+     * @return The current generation ID for this path
+     */
+    public int getGenerationId()  {
+        return nGetGenerationID(mNativePath);
+    }
+
+    /**
+     * Two paths can be interpolated, by calling {@link #interpolate(Path, float, Path)}, if they
+     * have exactly the same structure. That is, both paths must have the same
+     * operations, in the same order. If any of the operations are
+     * of type {@link PathIterator#VERB_CONIC}, then the weights of those conics must also match.
+     *
+     * @param otherPath The other <code>Path</code> being interpolated to from this one.
+     * @return true if interpolation is possible, false otherwise
+     */
+    public boolean isInterpolatable(@NonNull Path otherPath) {
+        return nIsInterpolatable(mNativePath, otherPath.mNativePath);
+    }
+
+    /**
+     * This method will linearly interpolate from this path to <code>otherPath</code> given
+     * the interpolation parameter <code>t</code>, returning the result in
+     * <code>interpolatedPath</code>. Interpolation will only succeed if the structures of the
+     * two paths match exactly, as discussed in {@link #isInterpolatable(Path)}.
+     *
+     * @param otherPath The other <code>Path</code> being interpolated to.
+     * @param t The interpolation parameter. A value of 0 results in a <code>Path</code>
+     *          equivalent to this path, a value of 1 results in one equivalent to
+     *          <code>otherPath</code>.
+     * @param interpolatedPath The interpolated results.
+     */
+    public boolean interpolate(@NonNull Path otherPath, float t, @NonNull Path interpolatedPath) {
+        return nInterpolate(mNativePath, otherPath.mNativePath, t, interpolatedPath.mNativePath);
+    }
+
     // ------------------ Regular JNI ------------------------
 
     private static native long nInit();
@@ -750,6 +844,10 @@
     private static native void nRLineTo(long nPath, float dx, float dy);
     private static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2);
     private static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2);
+    private static native void nConicTo(long nPath, float x1, float y1, float x2, float y2,
+            float weight);
+    private static native void nRConicTo(long nPath, float dx1, float dy1, float dx2, float dy2,
+            float weight);
     private static native void nCubicTo(long nPath, float x1, float y1, float x2, float y2,
             float x3, float y3);
     private static native void nRCubicTo(long nPath, float x1, float y1, float x2, float y2,
@@ -777,6 +875,8 @@
     private static native void nTransform(long nPath, long matrix);
     private static native boolean nOp(long path1, long path2, int op, long result);
     private static native float[] nApproximate(long nPath, float error);
+    private static native boolean nInterpolate(long startPath, long endPath, float t,
+            long interpolatedPath);
 
     // ------------------ Fast JNI ------------------------
 
@@ -786,6 +886,10 @@
     // ------------------ Critical JNI ------------------------
 
     @CriticalNative
+    private static native int nGetGenerationID(long nativePath);
+    @CriticalNative
+    private static native boolean nIsInterpolatable(long startPath, long endPath);
+    @CriticalNative
     private static native void nReset(long nPath);
     @CriticalNative
     private static native void nRewind(long nPath);
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
new file mode 100644
index 0000000..33b9a47
--- /dev/null
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.system.VMRuntime;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+/**
+ * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its
+ * operations and point values.
+ */
+public class PathIterator implements Iterator<PathIterator.Segment> {
+
+    private final float[] mPointsArray;
+    private final long mPointsAddress;
+    private int mCachedVerb = -1;
+    private boolean mDone = false;
+    private final long mNativeIterator;
+    private final Path mPath;
+    private final int mPathGenerationId;
+    private static final int POINT_ARRAY_SIZE = 8;
+
+    private static final NativeAllocationRegistry sRegistry =
+            NativeAllocationRegistry.createMalloced(
+                    PathIterator.class.getClassLoader(), nGetFinalizer());
+
+    /**
+     * The <code>Verb</code> indicates the operation for a given segment of a path. These
+     * operations correspond exactly to the primitive operations on {@link Path}, such as
+     * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}.
+     */
+    @Retention(SOURCE)
+    @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE})
+    @interface Verb {}
+    // these must match the values in SkPath.h
+    public static final int VERB_MOVE = 0;
+    public static final int VERB_LINE = 1;
+    public static final int VERB_QUAD = 2;
+    public static final int VERB_CONIC = 3;
+    public static final int VERB_CUBIC = 4;
+    public static final int VERB_CLOSE = 5;
+    public static final int VERB_DONE = 6;
+
+    /**
+     * Returns a {@link PathIterator} object for this path, which can be used to query the
+     * data (operations and points) in the path. Iterators can only be used on Path objects
+     * that have not been modified since the iterator was created. Calling
+     * {@link #next(float[], int)}, {@link #next()}, or {@link #hasNext()} on an
+     * iterator for a modified path will result in a {@link ConcurrentModificationException}.
+     *
+     * @param path The {@link Path} for which this iterator can be queried.
+     */
+    PathIterator(@NonNull Path path) {
+        mPath = path;
+        mNativeIterator = nCreate(mPath.mNativePath);
+        mPathGenerationId = mPath.getGenerationId();
+        final VMRuntime runtime = VMRuntime.getRuntime();
+        mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
+        mPointsAddress = runtime.addressOf(mPointsArray);
+        sRegistry.registerNativeAllocation(this, mNativeIterator);
+    }
+
+    /**
+     * Returns the next verb in this iterator's {@link Path}, and fills entries in the
+     * <code>points</code> array with the point data (if any) for that operation.
+     * Each two floats represent the data for a single point of that operation.
+     * The number of pairs of floats supplied in the resulting array depends on the verb:
+     * <ul>
+     * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
+     * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
+     * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
+     * <li>{@link #VERB_CONIC}: 3.5 pairs (indices 0 to 6), the seventh entry has the conic
+     * weight</li>
+     * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
+     * <li>{@link #VERB_CLOSE}: 0 pairs</li>
+     * <li>{@link #VERB_DONE}: 0 pairs</li>
+     * </ul>
+     * @param points The point data for this operation, must have at least
+     *               8 items available to hold up to 4 pairs of point values
+     * @param offset An offset into the <code>points</code> array where entries should be placed.
+     * @return the operation for the next element in the iteration
+     * @throws ArrayIndexOutOfBoundsException if the points array is too small
+     * @throws ConcurrentModificationException if the underlying path was modified
+     * since this iterator was created.
+     * @throws NoSuchElementException if the iteration has no more elements
+     */
+    @NonNull
+    public @Verb int next(@NonNull float[] points, int offset) {
+        if (points.length < offset + POINT_ARRAY_SIZE) {
+            throw new ArrayIndexOutOfBoundsException("points array must be able to "
+                    + "hold at least 8 entries");
+        }
+        @Verb int returnVerb = getReturnVerb(mCachedVerb);
+        mCachedVerb = -1;
+        System.arraycopy(mPointsArray, 0, points, offset, POINT_ARRAY_SIZE);
+        return returnVerb;
+    }
+
+    /**
+     * Returns true if the there are more elements in this iterator to be returned.
+     * A return value of <code>false</code> means there are no more elements, and an
+     * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will throw a
+     * {@link NoSuchElementException}.
+     *
+     * @return true if there are more elements to be iterated through, false otherwise
+     * @throws ConcurrentModificationException if the underlying path was modified
+     * since this iterator was created.
+     */
+    @Override
+    public boolean hasNext() {
+        try {
+            if (mCachedVerb == -1) {
+                mCachedVerb = nextInternal();
+            }
+            return true;
+        } catch (NoSuchElementException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the next verb in the iteration, or {@link #VERB_DONE} if there are no more
+     * elements.
+     *
+     * @return the next verb in the iteration, or {@link #VERB_DONE} if there are no more
+     * elements
+     * @throws ConcurrentModificationException if the underlying path was modified
+     * since this iterator was created.
+     */
+    @NonNull
+    public @Verb int peek() {
+        if (mPathGenerationId != mPath.getGenerationId()) {
+            throw new ConcurrentModificationException(
+                    "Iterator cannot be used on modified Path");
+        }
+        if (mDone) {
+            return VERB_DONE;
+        }
+        return nPeek(mNativeIterator);
+    }
+
+    /**
+     * This is where the work is done for {@link #next()}. Using this internal method
+     * is helfpul for managing the cached segment used by {@link #hasNext()}.
+     *
+     * @return the segment to be returned by {@link #next()}
+     * @throws NoSuchElementException if the iteration has no more elements
+     */
+    @NonNull
+    private @Verb int nextInternal() {
+        if (mDone) {
+            throw new NoSuchElementException("No more path segments to iterate");
+        }
+        if (mPathGenerationId != mPath.getGenerationId()) {
+            throw new ConcurrentModificationException(
+                    "Iterator cannot be used on modified Path");
+        }
+        @Verb int verb = nNext(mNativeIterator, mPointsAddress);
+        if (verb == VERB_DONE) {
+            mDone = true;
+        }
+        return verb;
+    }
+
+    /**
+     * Returns the next {@link Segment} element in this iterator.
+     *
+     * There are two versions of <code>next()</code>. This version is slightly more
+     * expensive at runtime, since it allocates a new {@link Segment} object with
+     * every call. The other version, {@link #next(float[], int)} requires no such allocation, but
+     * requires a little more manual effort to use.
+     *
+     * @return the next segment in this iterator
+     * @throws NoSuchElementException if the iteration has no more elements
+     * @throws ConcurrentModificationException if the underlying path was modified
+     * since this iterator was created.
+     */
+    @NonNull
+    @Override
+    public Segment next() {
+        @Verb int returnVerb = getReturnVerb(mCachedVerb);
+        mCachedVerb = -1;
+        float conicWeight = 0f;
+        if (returnVerb == VERB_CONIC) {
+            conicWeight = mPointsArray[6];
+        }
+        float[] returnPoints = new float[8];
+        System.arraycopy(mPointsArray, 0, returnPoints, 0, POINT_ARRAY_SIZE);
+        return new Segment(returnVerb, returnPoints, conicWeight);
+    }
+
+    private @Verb int getReturnVerb(int cachedVerb) {
+        switch (cachedVerb) {
+            case VERB_MOVE: return VERB_MOVE;
+            case VERB_LINE: return VERB_LINE;
+            case VERB_QUAD: return VERB_QUAD;
+            case VERB_CONIC: return VERB_CONIC;
+            case VERB_CUBIC: return VERB_CUBIC;
+            case VERB_CLOSE: return VERB_CLOSE;
+            case VERB_DONE: return VERB_DONE;
+        }
+        return nextInternal();
+    }
+
+    /**
+     * This class holds the data for a given segment in a path, as returned by
+     * {@link #next()}.
+     */
+    public static class Segment {
+        private final @Verb int mVerb;
+        private final float[] mPoints;
+        private final float mConicWeight;
+
+        /**
+         * The operation for this segment.
+         *
+         * @return the verb which indicates the operation happening in this segment
+         */
+        @NonNull
+        public @Verb int getVerb() {
+            return mVerb;
+        }
+
+        /**
+         * The point data for this segment.
+         *
+         * Each two floats represent the data for a single point of that operation.
+         * The number of pairs of floats supplied in the resulting array depends on the verb:
+         * <ul>
+         * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
+         * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
+         * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
+         * <li>{@link #VERB_CONIC}: 4 pairs (indices 0 to 7), the last pair contains the
+         * conic weight twice</li>
+         * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
+         * <li>{@link #VERB_CLOSE}: 0 pairs</li>
+         * <li>{@link #VERB_DONE}: 0 pairs</li>
+         * </ul>
+         * @return the point data for this segment
+         */
+        @NonNull
+        public float[] getPoints() {
+            return mPoints;
+        }
+
+        /**
+         * The weight for the conic operation in this segment. If the verb in this segment
+         * is not equal to {@link #VERB_CONIC}, the weight value is undefined.
+         *
+         * @see Path#conicTo(float, float, float, float, float)
+         * @return the weight for the conic operation in this segment, if any
+         */
+        public float getConicWeight() {
+            return mConicWeight;
+        }
+
+        public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+            mVerb = verb;
+            mPoints = points;
+            mConicWeight = conicWeight;
+        }
+    }
+
+    // ------------------ Regular JNI ------------------------
+
+    private static native long nCreate(long nativePath);
+    private static native long nGetFinalizer();
+
+    // ------------------ Critical JNI ------------------------
+
+    @CriticalNative
+    private static native int nNext(long nativeIterator, long pointsAddress);
+
+    @CriticalNative
+    private static native int nPeek(long nativeIterator);
+}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
deleted file mode 100644
index c7062e0..0000000
--- a/ktfmt_includes.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-packages/SystemUI/compose/
-packages/SystemUI/screenshot/
-packages/SystemUI/src/com/android/systemui/people/data
-packages/SystemUI/src/com/android/systemui/people/ui
-packages/SystemUI/src/com/android/systemui/keyguard/data
-packages/SystemUI/src/com/android/systemui/keyguard/dagger
-packages/SystemUI/src/com/android/systemui/keyguard/domain
-packages/SystemUI/src/com/android/systemui/keyguard/shared
-packages/SystemUI/src/com/android/systemui/keyguard/ui
-packages/SystemUI/src/com/android/systemui/qs/footer
-packages/SystemUI/src/com/android/systemui/security
-packages/SystemUI/src/com/android/systemui/common/
-packages/SystemUI/tests/utils/src/com/android/systemui/qs/
-packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
-packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt
-packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt
-packages/SystemUI/tests/src/com/android/systemui/qs/footer/
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dcbb272..d63c25d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1387,9 +1387,6 @@
 
             if (update.selectionChanged && mStackView != null) {
                 mStackView.setSelectedBubble(update.selectedBubble);
-                if (update.selectedBubble != null) {
-                    mSysuiProxy.updateNotificationSuppression(update.selectedBubble.getKey());
-                }
             }
 
             // Expanding? Apply this last.
@@ -1448,7 +1445,6 @@
                     // in the shade, it is essentially removed.
                     Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey());
                     if (bubbleChild != null) {
-                        mSysuiProxy.removeNotificationEntry(bubbleChild.getKey());
                         bubbleChild.setSuppressNotification(true);
                         bubbleChild.setShowDot(false /* show */);
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 0e97e9e..453b34e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -278,12 +278,8 @@
 
         void notifyMaybeCancelSummary(String key);
 
-        void removeNotificationEntry(String key);
-
         void updateNotificationBubbleButton(String key);
 
-        void updateNotificationSuppression(String key);
-
         void onStackExpandChanged(boolean shouldExpand);
 
         void onManageMenuExpandChanged(boolean menuExpanded);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 0d75bc4..f1465f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -105,8 +105,8 @@
         state.mTaskInfo = taskInfo;
         mTasks.put(taskInfo.taskId, state);
 
-        updateRecentsForVisibleFullscreenTask(taskInfo);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        updateRecentsForVisibleFullscreenTask(taskInfo);
         if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             state.mWindowDecoration =
@@ -135,8 +135,8 @@
             mWindowDecorViewModelOptional.get().onTaskInfoChanged(
                     state.mTaskInfo, state.mWindowDecoration);
         }
-        updateRecentsForVisibleFullscreenTask(taskInfo);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        updateRecentsForVisibleFullscreenTask(taskInfo);
 
         final Point positionInParent = state.mTaskInfo.positionInParent;
         if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 67d7aca..298bf68 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -86,8 +86,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 3)
+                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
 
         const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 293eb7c..47557bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
 import android.view.WindowInsets
 import android.view.WindowManager
 import androidx.test.filters.RequiresDevice
@@ -92,7 +91,7 @@
             }
         }
 
-    @Presubmit
+    @FlakyTest(bugId = 242088970)
     @Test
     fun testAppIsVisibleAtEnd() {
         testSpec.assertLayersEnd {
@@ -125,7 +124,7 @@
         super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 242088970)
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() =
         super.taskBarLayerIsVisibleAtStartAndEnd()
@@ -135,4 +134,28 @@
     @Test
     override fun taskBarWindowIsAlwaysVisible() =
         super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 242088970)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 242088970)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 242088970)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 242088970)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index d194472..6fcd17a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -185,8 +185,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 3
+                    supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 507562b..a99439e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -246,8 +246,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 3
+                    supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index b5a3c78..39a7017 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -100,7 +100,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
+                    supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 1d26614..421a6fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -125,7 +125,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
+                supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index b71a9d8..3bffef0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -86,8 +86,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 3)
+                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 31a39c1..75d25e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -105,8 +105,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                            repetitions = 3)
+                    .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fd661cf..825aca3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -178,8 +178,7 @@
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 3
+                    supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index b3f0fb9..d3e2ce15 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -89,7 +89,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3
+                supportedRotations = listOf(Surface.ROTATION_0)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 8bd5c54..3d64bbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -105,7 +105,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3
+                supportedRotations = listOf(Surface.ROTATION_0)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 454927e..be39fae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -116,8 +116,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 3)
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 09248a1..4dc9858 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -264,8 +264,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                repetitions = 3
+                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 6d64cb9..d5de22f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -224,8 +224,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
-                    repetitions = 1)
+                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d238814..6cbb685 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -178,7 +178,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index ba40c27..581826e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -195,7 +195,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 6828589..5c051e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -182,7 +182,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 9ac7c23..9ca9ab0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -186,7 +186,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 supportedRotations = listOf(Surface.ROTATION_0),
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 8401c1a..8e2769f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -180,7 +180,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 168afda..531d376 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -195,7 +195,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index c1fce5f..ea43c7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -183,7 +183,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
             )
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 1530561..525e09a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -180,7 +180,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 20544bd..c030603 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -184,7 +184,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 5a8604f..b8565f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -183,7 +183,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index adea66a..20d7f2c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -185,7 +185,6 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ad9aa6c..b11e542 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -93,6 +93,10 @@
 
 cc_defaults {
     name: "hwui_static_deps",
+    defaults: [
+        "android.hardware.graphics.common-ndk_shared",
+        "android.hardware.graphics.composer3-ndk_shared",
+    ],
     shared_libs: [
         "libbase",
         "libharfbuzz_ng",
@@ -106,9 +110,7 @@
     target: {
         android: {
             shared_libs: [
-                "android.hardware.graphics.common-V3-ndk",
                 "android.hardware.graphics.common@1.2",
-                "android.hardware.graphics.composer3-V1-ndk",
                 "liblog",
                 "libcutils",
                 "libutils",
@@ -345,6 +347,7 @@
         "jni/PaintFilter.cpp",
         "jni/Path.cpp",
         "jni/PathEffect.cpp",
+        "jni/PathIterator.cpp",
         "jni/PathMeasure.cpp",
         "jni/Picture.cpp",
         "jni/Region.cpp",
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 942c050..b7a1563 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -53,6 +53,7 @@
 extern int register_android_graphics_Matrix(JNIEnv* env);
 extern int register_android_graphics_Paint(JNIEnv* env);
 extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(JNIEnv* env);
 extern int register_android_graphics_PathMeasure(JNIEnv* env);
 extern int register_android_graphics_Picture(JNIEnv* env);
 extern int register_android_graphics_Region(JNIEnv* env);
@@ -100,6 +101,7 @@
         {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
         {"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
         {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
+        {"android.graphics.PathIterator", REG_JNI(register_android_graphics_PathIterator)},
         {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
         {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
         {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd7..39725a5 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -59,6 +59,7 @@
 extern int register_android_graphics_Matrix(JNIEnv* env);
 extern int register_android_graphics_Paint(JNIEnv* env);
 extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(JNIEnv* env);
 extern int register_android_graphics_PathMeasure(JNIEnv* env);
 extern int register_android_graphics_Picture(JNIEnv*);
 extern int register_android_graphics_Region(JNIEnv* env);
@@ -94,59 +95,60 @@
     };
 #endif
 
-static const RegJNIRec gRegJNI[] = {
-    REG_JNI(register_android_graphics_Canvas),
-    // This needs to be before register_android_graphics_Graphics, or the latter
-    // will not be able to find the jmethodID for ColorSpace.get().
-    REG_JNI(register_android_graphics_ColorSpace),
-    REG_JNI(register_android_graphics_Graphics),
-    REG_JNI(register_android_graphics_Bitmap),
-    REG_JNI(register_android_graphics_BitmapFactory),
-    REG_JNI(register_android_graphics_BitmapRegionDecoder),
-    REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
-    REG_JNI(register_android_graphics_Camera),
-    REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
-    REG_JNI(register_android_graphics_CanvasProperty),
-    REG_JNI(register_android_graphics_ColorFilter),
-    REG_JNI(register_android_graphics_DrawFilter),
-    REG_JNI(register_android_graphics_FontFamily),
-    REG_JNI(register_android_graphics_HardwareRendererObserver),
-    REG_JNI(register_android_graphics_ImageDecoder),
-    REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
-    REG_JNI(register_android_graphics_Interpolator),
-    REG_JNI(register_android_graphics_MaskFilter),
-    REG_JNI(register_android_graphics_Matrix),
-    REG_JNI(register_android_graphics_Movie),
-    REG_JNI(register_android_graphics_NinePatch),
-    REG_JNI(register_android_graphics_Paint),
-    REG_JNI(register_android_graphics_Path),
-    REG_JNI(register_android_graphics_PathMeasure),
-    REG_JNI(register_android_graphics_PathEffect),
-    REG_JNI(register_android_graphics_Picture),
-    REG_JNI(register_android_graphics_Region),
-    REG_JNI(register_android_graphics_Shader),
-    REG_JNI(register_android_graphics_RenderEffect),
-    REG_JNI(register_android_graphics_TextureLayer),
-    REG_JNI(register_android_graphics_Typeface),
-    REG_JNI(register_android_graphics_YuvImage),
-    REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
-    REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
-    REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
-    REG_JNI(register_android_graphics_drawable_VectorDrawable),
-    REG_JNI(register_android_graphics_fonts_Font),
-    REG_JNI(register_android_graphics_fonts_FontFamily),
-    REG_JNI(register_android_graphics_pdf_PdfDocument),
-    REG_JNI(register_android_graphics_pdf_PdfEditor),
-    REG_JNI(register_android_graphics_pdf_PdfRenderer),
-    REG_JNI(register_android_graphics_text_MeasuredText),
-    REG_JNI(register_android_graphics_text_LineBreaker),
-    REG_JNI(register_android_graphics_text_TextShaper),
+    static const RegJNIRec gRegJNI[] = {
+            REG_JNI(register_android_graphics_Canvas),
+            // This needs to be before register_android_graphics_Graphics, or the latter
+            // will not be able to find the jmethodID for ColorSpace.get().
+            REG_JNI(register_android_graphics_ColorSpace),
+            REG_JNI(register_android_graphics_Graphics),
+            REG_JNI(register_android_graphics_Bitmap),
+            REG_JNI(register_android_graphics_BitmapFactory),
+            REG_JNI(register_android_graphics_BitmapRegionDecoder),
+            REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
+            REG_JNI(register_android_graphics_Camera),
+            REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
+            REG_JNI(register_android_graphics_CanvasProperty),
+            REG_JNI(register_android_graphics_ColorFilter),
+            REG_JNI(register_android_graphics_DrawFilter),
+            REG_JNI(register_android_graphics_FontFamily),
+            REG_JNI(register_android_graphics_HardwareRendererObserver),
+            REG_JNI(register_android_graphics_ImageDecoder),
+            REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
+            REG_JNI(register_android_graphics_Interpolator),
+            REG_JNI(register_android_graphics_MaskFilter),
+            REG_JNI(register_android_graphics_Matrix),
+            REG_JNI(register_android_graphics_Movie),
+            REG_JNI(register_android_graphics_NinePatch),
+            REG_JNI(register_android_graphics_Paint),
+            REG_JNI(register_android_graphics_Path),
+            REG_JNI(register_android_graphics_PathIterator),
+            REG_JNI(register_android_graphics_PathMeasure),
+            REG_JNI(register_android_graphics_PathEffect),
+            REG_JNI(register_android_graphics_Picture),
+            REG_JNI(register_android_graphics_Region),
+            REG_JNI(register_android_graphics_Shader),
+            REG_JNI(register_android_graphics_RenderEffect),
+            REG_JNI(register_android_graphics_TextureLayer),
+            REG_JNI(register_android_graphics_Typeface),
+            REG_JNI(register_android_graphics_YuvImage),
+            REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
+            REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
+            REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
+            REG_JNI(register_android_graphics_drawable_VectorDrawable),
+            REG_JNI(register_android_graphics_fonts_Font),
+            REG_JNI(register_android_graphics_fonts_FontFamily),
+            REG_JNI(register_android_graphics_pdf_PdfDocument),
+            REG_JNI(register_android_graphics_pdf_PdfEditor),
+            REG_JNI(register_android_graphics_pdf_PdfRenderer),
+            REG_JNI(register_android_graphics_text_MeasuredText),
+            REG_JNI(register_android_graphics_text_LineBreaker),
+            REG_JNI(register_android_graphics_text_TextShaper),
 
-    REG_JNI(register_android_util_PathParser),
-    REG_JNI(register_android_view_RenderNode),
-    REG_JNI(register_android_view_DisplayListCanvas),
-    REG_JNI(register_android_view_ThreadedRenderer),
-};
+            REG_JNI(register_android_util_PathParser),
+            REG_JNI(register_android_view_RenderNode),
+            REG_JNI(register_android_view_DisplayListCanvas),
+            REG_JNI(register_android_view_ThreadedRenderer),
+    };
 
 } // namespace android
 
diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp
index d67bcf2..3694ce0 100644
--- a/libs/hwui/jni/Path.cpp
+++ b/libs/hwui/jni/Path.cpp
@@ -102,6 +102,18 @@
         obj->rQuadTo(dx1, dy1, dx2, dy2);
     }
 
+    static void conicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2,
+                        jfloat y2, jfloat weight) {
+        SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+        obj->conicTo(x1, y1, x2, y2, weight);
+    }
+
+    static void rConicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1,
+                         jfloat dx2, jfloat dy2, jfloat weight) {
+        SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+        obj->rConicTo(dx1, dy1, dx2, dy2, weight);
+    }
+
     static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1,
             jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -209,6 +221,14 @@
         obj->setLastPt(dx, dy);
     }
 
+    static jboolean interpolate(JNIEnv* env, jclass clazz, jlong startHandle, jlong endHandle,
+                                jfloat t, jlong interpolatedHandle) {
+        SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+        SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+        SkPath* interpolatedPath = reinterpret_cast<SkPath*>(interpolatedHandle);
+        return startPath->interpolate(*endPath, t, interpolatedPath);
+    }
+
     static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle,
             jlong dstHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -473,6 +493,16 @@
 
     // ---------------- @CriticalNative -------------------------
 
+    static jint getGenerationID(CRITICAL_JNI_PARAMS_COMMA jlong pathHandle) {
+        return (reinterpret_cast<SkPath*>(pathHandle)->getGenerationID());
+    }
+
+    static jboolean isInterpolatable(CRITICAL_JNI_PARAMS_COMMA jlong startHandle, jlong endHandle) {
+        SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+        SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+        return startPath->isInterpolatable(*endPath);
+    }
+
     static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->reset();
@@ -506,48 +536,53 @@
 };
 
 static const JNINativeMethod methods[] = {
-    {"nInit","()J", (void*) SkPathGlue::init},
-    {"nInit","(J)J", (void*) SkPathGlue::init_Path},
-    {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer},
-    {"nSet","(JJ)V", (void*) SkPathGlue::set},
-    {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds},
-    {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve},
-    {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF},
-    {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo},
-    {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF},
-    {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo},
-    {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF},
-    {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo},
-    {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF},
-    {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo},
-    {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo},
-    {"nClose","(J)V", (void*) SkPathGlue::close},
-    {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect},
-    {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval},
-    {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle},
-    {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc},
-    {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY},
-    {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8},
-    {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF},
-    {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path},
-    {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix},
-    {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF},
-    {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint},
-    {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath},
-    {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix},
-    {"nOp","(JJIJ)Z", (void*) SkPathGlue::op},
-    {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate},
+        {"nInit", "()J", (void*)SkPathGlue::init},
+        {"nInit", "(J)J", (void*)SkPathGlue::init_Path},
+        {"nGetFinalizer", "()J", (void*)SkPathGlue::getFinalizer},
+        {"nSet", "(JJ)V", (void*)SkPathGlue::set},
+        {"nComputeBounds", "(JLandroid/graphics/RectF;)V", (void*)SkPathGlue::computeBounds},
+        {"nIncReserve", "(JI)V", (void*)SkPathGlue::incReserve},
+        {"nMoveTo", "(JFF)V", (void*)SkPathGlue::moveTo__FF},
+        {"nRMoveTo", "(JFF)V", (void*)SkPathGlue::rMoveTo},
+        {"nLineTo", "(JFF)V", (void*)SkPathGlue::lineTo__FF},
+        {"nRLineTo", "(JFF)V", (void*)SkPathGlue::rLineTo},
+        {"nQuadTo", "(JFFFF)V", (void*)SkPathGlue::quadTo__FFFF},
+        {"nRQuadTo", "(JFFFF)V", (void*)SkPathGlue::rQuadTo},
+        {"nConicTo", "(JFFFFF)V", (void*)SkPathGlue::conicTo},
+        {"nRConicTo", "(JFFFFF)V", (void*)SkPathGlue::rConicTo},
+        {"nCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::cubicTo__FFFFFF},
+        {"nRCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::rCubicTo},
+        {"nArcTo", "(JFFFFFFZ)V", (void*)SkPathGlue::arcTo},
+        {"nClose", "(J)V", (void*)SkPathGlue::close},
+        {"nAddRect", "(JFFFFI)V", (void*)SkPathGlue::addRect},
+        {"nAddOval", "(JFFFFI)V", (void*)SkPathGlue::addOval},
+        {"nAddCircle", "(JFFFI)V", (void*)SkPathGlue::addCircle},
+        {"nAddArc", "(JFFFFFF)V", (void*)SkPathGlue::addArc},
+        {"nAddRoundRect", "(JFFFFFFI)V", (void*)SkPathGlue::addRoundRectXY},
+        {"nAddRoundRect", "(JFFFF[FI)V", (void*)SkPathGlue::addRoundRect8},
+        {"nAddPath", "(JJFF)V", (void*)SkPathGlue::addPath__PathFF},
+        {"nAddPath", "(JJ)V", (void*)SkPathGlue::addPath__Path},
+        {"nAddPath", "(JJJ)V", (void*)SkPathGlue::addPath__PathMatrix},
+        {"nInterpolate", "(JJFJ)Z", (void*)SkPathGlue::interpolate},
+        {"nOffset", "(JFF)V", (void*)SkPathGlue::offset__FF},
+        {"nSetLastPoint", "(JFF)V", (void*)SkPathGlue::setLastPoint},
+        {"nTransform", "(JJJ)V", (void*)SkPathGlue::transform__MatrixPath},
+        {"nTransform", "(JJ)V", (void*)SkPathGlue::transform__Matrix},
+        {"nOp", "(JJIJ)Z", (void*)SkPathGlue::op},
+        {"nApproximate", "(JF)[F", (void*)SkPathGlue::approximate},
 
-    // ------- @FastNative below here ----------------------
-    {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect},
+        // ------- @FastNative below here ----------------------
+        {"nIsRect", "(JLandroid/graphics/RectF;)Z", (void*)SkPathGlue::isRect},
 
-    // ------- @CriticalNative below here ------------------
-    {"nReset","(J)V", (void*) SkPathGlue::reset},
-    {"nRewind","(J)V", (void*) SkPathGlue::rewind},
-    {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty},
-    {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex},
-    {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType},
-    {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType},
+        // ------- @CriticalNative below here ------------------
+        {"nGetGenerationID", "(J)I", (void*)SkPathGlue::getGenerationID},
+        {"nIsInterpolatable", "(JJ)Z", (void*)SkPathGlue::isInterpolatable},
+        {"nReset", "(J)V", (void*)SkPathGlue::reset},
+        {"nRewind", "(J)V", (void*)SkPathGlue::rewind},
+        {"nIsEmpty", "(J)Z", (void*)SkPathGlue::isEmpty},
+        {"nIsConvex", "(J)Z", (void*)SkPathGlue::isConvex},
+        {"nGetFillType", "(J)I", (void*)SkPathGlue::getFillType},
+        {"nSetFillType", "(JI)V", (void*)SkPathGlue::setFillType},
 };
 
 int register_android_graphics_Path(JNIEnv* env) {
diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp
new file mode 100644
index 0000000..3884342
--- /dev/null
+++ b/libs/hwui/jni/PathIterator.cpp
@@ -0,0 +1,81 @@
+/* libs/android_runtime/android/graphics/PathMeasure.cpp
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <log/log.h>
+
+#include "GraphicsJNI.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+
+namespace android {
+
+class SkPathIteratorGlue {
+public:
+    static void finalizer(SkPath::RawIter* obj) { delete obj; }
+
+    static jlong getFinalizer(JNIEnv* env, jclass clazz) {
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer));
+    }
+
+    static jlong create(JNIEnv* env, jobject clazz, jlong pathHandle) {
+        const SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+        return reinterpret_cast<jlong>(new SkPath::RawIter(*path));
+    }
+
+    // ---------------- @CriticalNative -------------------------
+
+    static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) {
+        SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+        return iterator->peek();
+    }
+
+    static jint next(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle, jlong pointsArray) {
+        static_assert(SkPath::kMove_Verb == 0, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kLine_Verb == 1, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kQuad_Verb == 2, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kConic_Verb == 3, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kCubic_Verb == 4, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kClose_Verb == 5, "SkPath::Verb unexpected index");
+        static_assert(SkPath::kDone_Verb == 6, "SkPath::Verb unexpected index");
+
+        SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+        float* points = reinterpret_cast<float*>(pointsArray);
+        SkPath::Verb verb =
+                static_cast<SkPath::Verb>(iterator->next(reinterpret_cast<SkPoint*>(points)));
+        if (verb == SkPath::kConic_Verb) {
+            float weight = iterator->conicWeight();
+            points[6] = weight;
+        }
+        return static_cast<int>(verb);
+    }
+};
+
+static const JNINativeMethod methods[] = {
+        {"nCreate", "(J)J", (void*)SkPathIteratorGlue::create},
+        {"nGetFinalizer", "()J", (void*)SkPathIteratorGlue::getFinalizer},
+
+        // ------- @CriticalNative below here ------------------
+
+        {"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek},
+        {"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next},
+};
+
+int register_android_graphics_PathIterator(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/graphics/PathIterator", methods, NELEM(methods));
+}
+
+}  // namespace android
diff --git a/media/Android.bp b/media/Android.bp
index 7118afa..e97f077 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -35,8 +35,8 @@
         "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
     ],
     imports: [
-        "android.media.audio.common.types",
-        "android.media.soundtrigger.types",
+        "android.media.audio.common.types-V2",
+        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
@@ -232,12 +232,12 @@
         },
     },
     imports: [
-        "android.media.audio.common.types",
+        "android.media.audio.common.types-V2",
     ],
     versions_with_info: [
         {
             version: "1",
-            imports: ["android.media.audio.common.types-V1"],
+            imports: ["android.media.audio.common.types-V2"],
         },
     ],
 
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index c708876..40f6dc5 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -21,6 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,23 +41,29 @@
  * @hide
  * AudioDeviceVolumeManager provides access to audio device volume control.
  */
+@SystemApi
 public class AudioDeviceVolumeManager {
 
     // define when using Log.*
     //private static final String TAG = "AudioDeviceVolumeManager";
 
-    /** Indicates no special treatment in the handling of the volume adjustment */
+    /** @hide
+     * Indicates no special treatment in the handling of the volume adjustment */
     public static final int ADJUST_MODE_NORMAL = 0;
-    /** Indicates the start of a volume adjustment */
+    /** @hide
+     * Indicates the start of a volume adjustment */
     public static final int ADJUST_MODE_START = 1;
-    /** Indicates the end of a volume adjustment */
+    /** @hide
+     * Indicates the end of a volume adjustment */
     public static final int ADJUST_MODE_END = 2;
 
+    /** @hide */
     @IntDef(flag = false, prefix = "ADJUST_MODE", value = {
             ADJUST_MODE_NORMAL,
             ADJUST_MODE_START,
             ADJUST_MODE_END}
     )
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     public @interface VolumeAdjustmentMode {}
 
@@ -64,7 +72,16 @@
     private final @NonNull String mPackageName;
     private final @Nullable String mAttributionTag;
 
-    public AudioDeviceVolumeManager(Context context) {
+    /**
+     * Constructor
+     * @param context the Context for the device volume operations
+     */
+    @SuppressLint("ManagerConstructor")
+    // reason for suppression: even though the functionality handled by this class is implemented in
+    // AudioService, we want to avoid bloating android.media.AudioManager
+    // with @SystemApi functionality
+    public AudioDeviceVolumeManager(@NonNull Context context) {
+        Objects.requireNonNull(context);
         mPackageName = context.getApplicationContext().getOpPackageName();
         mAttributionTag = context.getApplicationContext().getAttributionTag();
     }
@@ -101,6 +118,7 @@
                 @VolumeAdjustmentMode int mode);
     }
 
+    /** @hide */
     static class ListenerInfo {
         final @NonNull OnAudioDeviceVolumeChangedListener mListener;
         final @NonNull Executor mExecutor;
@@ -127,6 +145,7 @@
     @GuardedBy("mDeviceVolumeListenerLock")
     private DeviceVolumeDispatcherStub mDeviceVolumeDispatcherStub;
 
+    /** @hide */
     final class DeviceVolumeDispatcherStub extends IAudioDeviceVolumeDispatcher.Stub {
         /**
          * Register / unregister the stub
@@ -305,6 +324,7 @@
      * @param vi the volume information, only stream-based volumes are supported
      * @param ada the device for which volume is to be modified
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
         try {
@@ -315,6 +335,7 @@
     }
 
     /**
+     * @hide
      * Return human-readable name for volume behavior
      * @param behavior one of the volume behaviors defined in AudioManager
      * @return a string for the given behavior
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 650f360..72190e3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -65,8 +65,6 @@
 
     private static final String TAG = "AudioSystem";
 
-    private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
-
     // private constructor to prevent instantiating AudioSystem
     private AudioSystem() {
         throw new UnsupportedOperationException("Trying to instantiate AudioSystem");
@@ -293,7 +291,7 @@
             case AUDIO_FORMAT_APTX_HD: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD;
             case AUDIO_FORMAT_LDAC: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC;
             case AUDIO_FORMAT_LC3: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3;
-            case AUDIO_FORMAT_OPUS: return SOURCE_CODEC_TYPE_OPUS; // TODO update in U
+            case AUDIO_FORMAT_OPUS: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS;
             default:
                 Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat)
                         + " for conversion to BT codec");
@@ -336,7 +334,7 @@
                 return AudioSystem.AUDIO_FORMAT_LDAC;
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3:
                 return AudioSystem.AUDIO_FORMAT_LC3;
-            case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS:
                 return AudioSystem.AUDIO_FORMAT_OPUS;
             default:
                 Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec)
diff --git a/media/java/android/media/VolumeInfo.java b/media/java/android/media/VolumeInfo.java
index c61b0e5..6b4f604 100644
--- a/media/java/android/media/VolumeInfo.java
+++ b/media/java/android/media/VolumeInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.audiopolicy.AudioVolumeGroup;
 import android.os.IBinder;
@@ -32,16 +33,13 @@
 
 /**
  * @hide
- * A class to represent type of volume information.
+ * A class to represent volume information.
  * Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
  * Volume index is optional when used to represent a category of volume.
  * Index ranges are supported too, making the representation of volume changes agnostic to the
  * range (e.g. can be used to map BT A2DP absolute volume range to internal range).
- *
- * Note: this class is not yet part of the SystemApi but is intended to be gradually introduced
- *       particularly in parts of the audio framework that suffer from code ambiguity when
- *       dealing with different volume ranges / units.
  */
+@SystemApi
 public final class VolumeInfo implements Parcelable {
     private static final String TAG = "VolumeInfo";
 
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 31fb8d0..9bf126b 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -35,7 +35,7 @@
     ISessionController getController();
     void setFlags(int flags);
     void setActive(boolean active);
-    void setMediaButtonReceiver(in PendingIntent mbr);
+    void setMediaButtonReceiver(in PendingIntent mbr, String sessionPackageName);
     void setMediaButtonBroadcastReceiver(in ComponentName broadcastReceiver);
     void setLaunchPendingIntent(in PendingIntent pi);
     void destroySession();
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1bd12af..9e265d8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -286,7 +286,7 @@
     @Deprecated
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
-            mBinder.setMediaButtonReceiver(mbr);
+            mBinder.setMediaButtonReceiver(mbr, mContext.getPackageName());
         } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
         }
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index 7fe2735..cd6ed23 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -381,7 +381,7 @@
 
     SkIRect cropIRect;
     cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
-    SkIRect* cropPtr = cropIRect.isEmpty() ? nullptr : &cropIRect;
+    SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
     return imageDecoder->setCropRect(cropPtr)
             ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
 }
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml
index 5689e34..a984938 100644
--- a/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_info.xml
@@ -19,7 +19,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="@android:color/system_accent1_200">
+        android:tint="@android:color/system_neutral1_200">
     <path android:fillColor="@android:color/white"
           android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_info.xml b/packages/CompanionDeviceManager/res/drawable/ic_info.xml
index d9f6a27..229960c 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_info.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_info.xml
@@ -20,7 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="@android:color/system_accent1_600">
+        android:tint="@android:color/system_neutral1_700">
     <path android:fillColor="@android:color/white"
           android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
index d0d46f7..1f922b9 100644
--- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
@@ -35,7 +35,7 @@
             <ImageView
                 android:id="@+id/app_icon"
                 android:layout_width="match_parent"
-                android:layout_height="48dp"
+                android:layout_height="32dp"
                 android:gravity="center"
                 android:layout_marginTop="12dp"
                 android:layout_marginBottom="12dp"/>
diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
index 14e7431..16a87e5 100644
--- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml
+++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
@@ -22,7 +22,7 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:layout_gravity="center"
-    android:paddingTop="24dp"
+    android:paddingTop="12dp"
     android:paddingBottom="4dp"
     android:paddingStart="24dp"
     android:paddingEnd="24dp"
@@ -32,24 +32,26 @@
         android:id="@+id/vendor_header_image"
         android:layout_width="48dp"
         android:layout_height="48dp"
+        android:padding="12dp"
         android:contentDescription="@string/vendor_icon_description" />
 
     <TextView
         android:id="@+id/vendor_header_name"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="12dp"
         android:layout_marginTop="12dp"
-        android:textSize="16sp"
+        android:textSize="14sp"
         android:layout_toEndOf="@+id/vendor_header_image"
         android:textColor="?android:attr/textColorSecondary"/>
 
     <ImageButton
         android:id="@+id/vendor_header_button"
-        android:background="@drawable/ic_info"
+        android:src="@drawable/ic_info"
         android:layout_width="48dp"
         android:layout_height="48dp"
+        android:padding="12dp"
         android:contentDescription="@string/vendor_header_button_description"
-        android:layout_alignParentEnd="true" />
+        android:layout_alignParentEnd="true"
+        android:background="@android:color/transparent" />
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 2000d96..428f2dc 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -49,6 +49,7 @@
     <style name="DescriptionSummary">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center</item>
         <item name="android:layout_marginTop">18dp</item>
         <item name="android:layout_marginLeft">18dp</item>
         <item name="android:layout_marginRight">18dp</item>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index 2da70d4..6e6da52 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -16,6 +16,6 @@
 
 package com.android.settingslib.spa.gallery
 
-import com.android.settingslib.spa.framework.SpaActivity
+import com.android.settingslib.spa.framework.BrowseActivity
 
-class MainActivity : SpaActivity(galleryPageRepository)
+class MainActivity : BrowseActivity(galleryPageProviders)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
similarity index 79%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/PageRepository.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
index 47e641f..3f37534 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spa.gallery
 
-import com.android.settingslib.spa.framework.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -24,16 +24,20 @@
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.page.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 
-val galleryPageRepository = SettingsPageRepository(
-    allPages = listOf(
+val galleryPageProviders = SettingsPageProviderRepository(
+    allPagesList = listOf(
         HomePageProvider,
         PreferencePageProvider,
         SwitchPreferencePageProvider,
         ArgumentPageProvider,
         SliderPageProvider,
+        SpinnerPageProvider,
         SettingsPagerPageProvider,
         FooterPageProvider,
     ),
-    startDestination = HomePageProvider.name,
+    rootPages = listOf(HomePageProvider.name)
 )
+
+// TODO: add other environment setup here.
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 912f8f8..089920c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -20,7 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -29,6 +29,7 @@
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.page.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
 
 object HomePageProvider : SettingsPageProvider {
@@ -48,6 +49,7 @@
         ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
 
         SliderPageProvider.EntryItem()
+        SpinnerPageProvider.EntryItem()
         SettingsPagerPageProvider.EntryItem()
         FooterPageProvider.EntryItem()
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 36361dd..bd366c3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -21,7 +21,7 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 82005ec..4a933ac 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -20,7 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index 0463e58..90dacdb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -29,7 +29,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index df48517..9d80818 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -19,7 +19,7 @@
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 04046fa..130cbd9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -27,14 +27,14 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.SettingsSlider
+import com.android.settingslib.spa.widget.SettingsSliderModel
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-import com.android.settingslib.spa.widget.ui.SettingsSlider
-import com.android.settingslib.spa.widget.ui.SettingsSliderModel
 
 private const val TITLE = "Sample Slider"
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
index e9e5d35..6d5d448 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -23,7 +23,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
new file mode 100644
index 0000000..7efa85b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.ui
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Spinner
+
+private const val TITLE = "Sample Spinner"
+
+object SpinnerPageProvider : SettingsPageProvider {
+    override val name = "Spinner"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SpinnerPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun SpinnerPage() {
+    RegularScaffold(title = TITLE) {
+        val selectedIndex = rememberSaveable { mutableStateOf(0) }
+        Spinner(
+            options = (1..3).map { "Option $it" },
+            selectedIndex = selectedIndex.value,
+            setIndex = { selectedIndex.value = it },
+        )
+        Preference(object : PreferenceModel {
+            override val title = "Selected index"
+            override val summary = remember { derivedStateOf { selectedIndex.value.toString() } }
+        })
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SpinnerPagePreview() {
+    SettingsTheme {
+        SpinnerPage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
similarity index 68%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 12be070..e8d1ea2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -21,17 +21,19 @@
 import androidx.activity.compose.setContent
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 
-open class SpaActivity(
-    private val settingsPageRepository: SettingsPageRepository,
+open class BrowseActivity(
+    private val sppRepository: SettingsPageProviderRepository,
 ) : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
@@ -46,13 +48,12 @@
 
     @Composable
     private fun MainContent() {
-        val startDestination =
-            intent?.getStringExtra(KEY_START_DESTINATION) ?: settingsPageRepository.startDestination
+        val destination = intent?.getStringExtra(KEY_DESTINATION)
 
         val navController = rememberNavController()
         CompositionLocalProvider(navController.localNavController()) {
-            NavHost(navController, startDestination) {
-                for (page in settingsPageRepository.allPages) {
+            NavHost(navController, sppRepository.getDefaultStartPageName()) {
+                for (page in sppRepository.getAllProviders()) {
                     composable(
                         route = page.route,
                         arguments = page.arguments,
@@ -61,6 +62,16 @@
                     }
                 }
             }
+
+            if (!destination.isNullOrEmpty()) {
+                LaunchedEffect(Unit) {
+                    navController.navigate(destination) {
+                        popUpTo(navController.graph.findStartDestination().id) {
+                            inclusive = true
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -68,6 +79,6 @@
         get() = name + arguments.joinToString("") { argument -> "/{${argument.name}}" }
 
     companion object {
-        const val KEY_START_DESTINATION = "spa:SpaActivity:startDestination"
+        const val KEY_DESTINATION = "spa:SpaActivity:destination"
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
deleted file mode 100644
index 4a270b1..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.api
-
-data class SettingsPageRepository(
-    val allPages: List<SettingsPageProvider>,
-    val startDestination: String,
-)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
similarity index 90%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 84daf22..2ab0cb9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework.api
+package com.android.settingslib.spa.framework.common
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
@@ -35,4 +35,6 @@
     /** The [Composable] used to render this page. */
     @Composable
     fun Page(arguments: Bundle?)
+
+    // fun buildEntry( arguments: Bundle?) : List<entry>
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepository.kt
new file mode 100644
index 0000000..0a32939
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+class SettingsPageProviderRepository(
+    allPagesList: List<SettingsPageProvider>,
+    private val rootPages: List<String>
+) {
+    // Maintains all SPA page providers.
+    private val allPages: Map<String, SettingsPageProvider> = allPagesList.associateBy { it.name }
+
+    fun getDefaultStartPageName(): String {
+        return rootPages.getOrElse(0) {
+            return ""
+        }
+    }
+
+    fun getAllProviders(): Collection<SettingsPageProvider> {
+        return allPages.values
+    }
+
+    fun getProviderOrNull(name: String): SettingsPageProvider? {
+        return allPages[name]
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index c68d5de..32ef0bb 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -16,25 +16,35 @@
 
 package com.android.settingslib.spa.framework.compose
 
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ProvidedValue
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.remember
 import androidx.navigation.NavHostController
 
 interface NavControllerWrapper {
     fun navigate(route: String)
-    fun navigateUp()
+    fun navigateBack()
 }
 
 @Composable
-fun NavHostController.localNavController() =
-    LocalNavController provides remember { NavControllerWrapperImpl(this) }
+fun NavHostController.localNavController(): ProvidedValue<NavControllerWrapper> {
+    val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
+    return LocalNavController provides remember {
+        NavControllerWrapperImpl(
+            navController = this,
+            onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher,
+        )
+    }
+}
 
 val LocalNavController = compositionLocalOf<NavControllerWrapper> {
     object : NavControllerWrapper {
         override fun navigate(route: String) {}
 
-        override fun navigateUp() {}
+        override fun navigateBack() {}
     }
 }
 
@@ -46,12 +56,13 @@
 
 internal class NavControllerWrapperImpl(
     private val navController: NavHostController,
+    private val onBackPressedDispatcher: OnBackPressedDispatcher?,
 ) : NavControllerWrapper {
     override fun navigate(route: String) {
         navController.navigate(route)
     }
 
-    override fun navigateUp() {
-        navController.navigateUp()
+    override fun navigateBack() {
+        onBackPressedDispatcher?.onBackPressed()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
index 27fdc91..bc316f7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -31,6 +31,10 @@
     val secondaryText: Color = Color.Unspecified,
     val primaryContainer: Color = Color.Unspecified,
     val onPrimaryContainer: Color = Color.Unspecified,
+    val spinnerHeaderContainer: Color = Color.Unspecified,
+    val onSpinnerHeaderContainer: Color = Color.Unspecified,
+    val spinnerItemContainer: Color = Color.Unspecified,
+    val onSpinnerItemContainer: Color = Color.Unspecified,
 )
 
 internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
@@ -65,6 +69,10 @@
         secondaryText = tonalPalette.neutralVariant30,
         primaryContainer = tonalPalette.primary90,
         onPrimaryContainer = tonalPalette.neutral10,
+        spinnerHeaderContainer = tonalPalette.primary90,
+        onSpinnerHeaderContainer = tonalPalette.neutral10,
+        spinnerItemContainer = tonalPalette.secondary90,
+        onSpinnerItemContainer = tonalPalette.neutralVariant30,
     )
 }
 
@@ -87,5 +95,9 @@
         secondaryText = tonalPalette.neutralVariant80,
         primaryContainer = tonalPalette.secondary90,
         onPrimaryContainer = tonalPalette.neutral10,
+        spinnerHeaderContainer = tonalPalette.primary90,
+        onSpinnerHeaderContainer = tonalPalette.neutral10,
+        spinnerItemContainer = tonalPalette.secondary90,
+        onSpinnerItemContainer = tonalPalette.neutralVariant30,
     )
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt
similarity index 98%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt
index 0454ac3..4f77a89 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.widget.ui
+package com.android.settingslib.spa.widget
 
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.AccessAlarm
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index c960254..b8e4360 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -26,13 +26,13 @@
 import com.android.settingslib.spa.framework.compose.LocalNavController
 
 @Composable
-internal fun NavigateUp() {
+internal fun NavigateBack() {
     val navController = LocalNavController.current
     val contentDescription = stringResource(
         id = androidx.appcompat.R.string.abc_action_bar_up_description,
     )
     BackAction(contentDescription) {
-        navController.navigateUp()
+        navController.navigateBack()
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index ee453f2..8b530b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -49,7 +49,7 @@
                         modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
                     )
                 },
-                navigationIcon = { NavigateUp() },
+                navigationIcon = { NavigateBack() },
                 actions = actions,
                 colors = settingsTopAppBarColors(),
             )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
new file mode 100644
index 0000000..429b81a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowDropDown
+import androidx.compose.material.icons.outlined.ArrowDropUp
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun Spinner(options: List<String>, selectedIndex: Int, setIndex: (index: Int) -> Unit) {
+    if (options.isEmpty()) {
+        return
+    }
+
+    var expanded by rememberSaveable { mutableStateOf(false) }
+
+    Box(
+        modifier = Modifier
+            .padding(SettingsDimension.itemPadding)
+            .selectableGroup(),
+    ) {
+        val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
+        Button(
+            onClick = { expanded = true },
+            modifier = Modifier.height(36.dp),
+            colors = ButtonDefaults.buttonColors(
+                containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
+                contentColor = SettingsTheme.colorScheme.onSpinnerHeaderContainer,
+            ),
+            contentPadding = contentPadding,
+        ) {
+            SpinnerText(options[selectedIndex])
+            Icon(
+                imageVector = when {
+                    expanded -> Icons.Outlined.ArrowDropUp
+                    else -> Icons.Outlined.ArrowDropDown
+                },
+                contentDescription = null,
+            )
+        }
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false },
+            modifier = Modifier.background(SettingsTheme.colorScheme.spinnerItemContainer),
+            offset = DpOffset(x = 0.dp, y = 4.dp),
+        ) {
+            options.forEachIndexed { index, option ->
+                DropdownMenuItem(
+                    text = {
+                        SpinnerText(
+                            text = option,
+                            modifier = Modifier.padding(end = 24.dp),
+                            color = SettingsTheme.colorScheme.onSpinnerItemContainer,
+                        )
+                    },
+                    onClick = {
+                        expanded = false
+                        setIndex(index)
+                    },
+                    contentPadding = contentPadding,
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun SpinnerText(
+    text: String,
+    modifier: Modifier = Modifier,
+    color: Color = Color.Unspecified,
+) {
+    Text(
+        text = text,
+        modifier = modifier.padding(end = SettingsDimension.itemPaddingEnd),
+        color = color,
+        style = MaterialTheme.typography.labelLarge,
+    )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SpinnerPreview() {
+    SettingsTheme {
+        var selectedIndex by rememberSaveable { mutableStateOf(0) }
+        Spinner(
+            options = (1..3).map { "Option $it" },
+            selectedIndex = selectedIndex,
+            setIndex = { selectedIndex = it },
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 037d8c4..1ce49fa 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,6 +31,7 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+        "truth-prebuilt",
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index be5a5ec..5f93a9f 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -63,5 +63,6 @@
     androidTestImplementation(project(":spa"))
     androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
     androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
+    androidTestImplementation 'com.google.truth:truth:1.1.3'
     androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt
new file mode 100644
index 0000000..1d95e33
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsSliderTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            SettingsSlider(object : SettingsSliderModel {
+                override val title = "Slider"
+                override val initValue = 40
+            })
+        }
+
+        composeTestRule.onNodeWithText("Slider").assertIsDisplayed()
+    }
+
+    // TODO: Add more unit tests for SettingsSlider widget.
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt
new file mode 100644
index 0000000..6c56d63
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpinnerTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun spinner_initialState() {
+        var selectedIndex by mutableStateOf(0)
+        composeTestRule.setContent {
+            Spinner(
+                options = (1..3).map { "Option $it" },
+                selectedIndex = selectedIndex,
+                setIndex = { selectedIndex = it },
+            )
+        }
+
+        composeTestRule.onNodeWithText("Option 1").assertIsDisplayed()
+        composeTestRule.onNodeWithText("Option 2").assertDoesNotExist()
+        assertThat(selectedIndex).isEqualTo(0)
+    }
+
+    @Test
+    fun spinner_canChangeState() {
+        var selectedIndex by mutableStateOf(0)
+        composeTestRule.setContent {
+            Spinner(
+                options = (1..3).map { "Option $it" },
+                selectedIndex = selectedIndex,
+                setIndex = { selectedIndex = it },
+            )
+        }
+
+        composeTestRule.onNodeWithText("Option 1").performClick()
+        composeTestRule.onNodeWithText("Option 2").performClick()
+
+        composeTestRule.onNodeWithText("Option 1").assertDoesNotExist()
+        composeTestRule.onNodeWithText("Option 2").assertIsDisplayed()
+        assertThat(selectedIndex).isEqualTo(1)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 298ecd5..eb9ce5e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -28,9 +28,8 @@
 import androidx.compose.ui.res.stringResource
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
@@ -44,7 +43,7 @@
 private const val USER_ID = "userId"
 
 internal class TogglePermissionAppInfoPageProvider(
-    private val factory: TogglePermissionAppListModelFactory,
+    private val appListTemplate: TogglePermissionAppListTemplate,
 ) : SettingsPageProvider {
     override val name = NAME
 
@@ -57,10 +56,10 @@
     @Composable
     override fun Page(arguments: Bundle?) {
         checkNotNull(arguments)
-        val permission = checkNotNull(arguments.getString(PERMISSION))
+        val permissionType = checkNotNull(arguments.getString(PERMISSION))
         val packageName = checkNotNull(arguments.getString(PACKAGE_NAME))
         val userId = arguments.getInt(USER_ID)
-        val listModel = rememberContext { context -> factory.createModel(permission, context) }
+        val listModel = appListTemplate.rememberModel(permissionType)
         TogglePermissionAppInfoPage(listModel, packageName, userId)
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
similarity index 71%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 70ff9a4..3cc5854 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -21,7 +21,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.res.stringResource
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spa.widget.preference.Preference
@@ -47,20 +47,14 @@
     fun setAllowed(record: T, newAllowed: Boolean)
 }
 
-interface TogglePermissionAppListModelFactory {
-    fun createModel(
-        permission: String,
-        context: Context,
-    ): TogglePermissionAppListModel<out AppRecord>
+interface TogglePermissionAppListProvider {
+    val permissionType: String
 
-    fun createPageProviders(): List<SettingsPageProvider> = listOf(
-        TogglePermissionAppListPageProvider(this),
-        TogglePermissionAppInfoPageProvider(this),
-    )
+    fun createModel(context: Context): TogglePermissionAppListModel<out AppRecord>
 
     @Composable
-    fun EntryItem(permissionType: String) {
-        val listModel = rememberModel(permissionType)
+    fun EntryItem() {
+        val listModel = rememberContext(::createModel)
         Preference(
             object : PreferenceModel {
                 override val title = stringResource(listModel.pageTitleResId)
@@ -68,8 +62,28 @@
             }
         )
     }
+
+    /**
+     * Gets the route to the toggle permission App List page.
+     *
+     * Expose route to enable enter from non-SPA pages.
+     */
+    fun getRoute(): String =
+        TogglePermissionAppListPageProvider.getRoute(permissionType)
 }
 
-@Composable
-internal fun TogglePermissionAppListModelFactory.rememberModel(permission: String) =
-    rememberContext { context -> createModel(permission, context) }
+class TogglePermissionAppListTemplate(
+    allProviders: List<TogglePermissionAppListProvider>,
+) {
+    private val listModelProviderMap = allProviders.associateBy { it.permissionType }
+
+    fun createPageProviders(): List<SettingsPageProvider> = listOf(
+        TogglePermissionAppListPageProvider(this),
+        TogglePermissionAppInfoPageProvider(this),
+    )
+
+    @Composable
+    internal fun rememberModel(permissionType: String) = rememberContext { context ->
+        listModelProviderMap.getValue(permissionType).createModel(context)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 107bf94..f2eb962 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -27,7 +27,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -38,7 +38,7 @@
 private const val PERMISSION = "permission"
 
 internal class TogglePermissionAppListPageProvider(
-    private val factory: TogglePermissionAppListModelFactory,
+    private val appListTemplate: TogglePermissionAppListTemplate,
 ) : SettingsPageProvider {
     override val name = NAME
 
@@ -55,7 +55,7 @@
 
     @Composable
     private fun TogglePermissionAppList(permissionType: String) {
-        val listModel = factory.rememberModel(permissionType)
+        val listModel = appListTemplate.rememberModel(permissionType)
         val context = LocalContext.current
         val internalListModel = remember {
             TogglePermissionInternalAppListModel(context, listModel)
@@ -75,8 +75,14 @@
     }
 
     companion object {
+        /**
+         * Gets the route to this page.
+         *
+         * Expose route to enable enter from non-SPA pages.
+         */
+        internal fun getRoute(permissionType: String) = "$NAME/$permissionType"
         @Composable
-        internal fun navigator(permissionType: String) = navigator(route = "$NAME/$permissionType")
+        internal fun navigator(permissionType: String) = navigator(route = getRoute(permissionType))
     }
 }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 1940986..b64dcca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -43,8 +43,6 @@
 public class A2dpProfile implements LocalBluetoothProfile {
     private static final String TAG = "A2dpProfile";
 
-    private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
-
     private Context mContext;
 
     private BluetoothA2dp mService;
@@ -333,7 +331,7 @@
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3:
                 index = 6;
                 break;
-            case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS:
                 index = 7;
                 break;
            }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 78dea89..abcd65b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -85,7 +85,6 @@
     <uses-permission android:name="android.permission.CONTROL_VPN" />
     <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
-    <uses-permission android:name="android.permission.NETWORK_STACK"/>
     <!-- Physical hardware -->
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -152,6 +151,9 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
 
+    <!-- For auto-grant the access to the Settings' slice preferences, e.g. volume slices. -->
+    <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
+
     <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
     <uses-permission android:name="android.permission.SET_WALLPAPER"/>
 
@@ -956,5 +958,13 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".volume.VolumePanelDialogReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.panel.action.VOLUME" />
+                <action android:name="com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG" />
+                <action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
new file mode 100644
index 0000000..3ca8dfe
--- /dev/null
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -0,0 +1,870 @@
++packages/SystemUI
+-packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+-packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+-packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+-packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
+-packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+-packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+-packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+-packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
+-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+-packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+-packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
+-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+-packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
+-packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
+-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
+-packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+-packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+-packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
+-packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+-packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+-packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+-packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+-packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
+-packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+-packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+-packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
+-packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+-packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
+-packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
+-packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+-packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt
+-packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
+-packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+-packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+-packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt
+-packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+-packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+-packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+-packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+-packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+-packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
+-packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+-packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+-packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt
+-packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
+-packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+-packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+-packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+-packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+-packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+-packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+-packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+-packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+-packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
+-packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+-packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
+-packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapper.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
+-packages/SystemUI/src/com/android/systemui/controls/controller/StatefulControlSubscriber.kt
+-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt
+-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt
+-packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+-packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+-packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+-packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+-packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
+-packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+-packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+-packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt
+-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
+-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+-packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
+-packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+-packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+-packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt
+-packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+-packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+-packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+-packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+-packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+-packages/SystemUI/src/com/android/systemui/dump/LogBufferFreezer.kt
+-packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+-packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+-packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+-packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+-packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+-packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+-packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+-packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
+-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+-packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
+-packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+-packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
+-packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
+-packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+-packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
+-packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+-packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
+-packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+-packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+-packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+-packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+-packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+-packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
+-packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
+-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
+-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
+-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+-packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
+-packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
+-packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt
+-packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+-packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+-packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+-packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+-packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+-packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+-packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+-packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+-packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+-packages/SystemUI/src/com/android/systemui/qs/QSExpansionPathInterpolator.kt
+-packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+-packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
+-packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
+-packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
+-packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
+-packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
+-packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt
+-packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
+-packages/SystemUI/src/com/android/systemui/qs/external/QSExternalModule.kt
+-packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+-packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt
+-packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+-packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+-packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
+-packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
+-packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+-packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+-packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+-packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+-packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+-packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+-packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+-packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+-packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+-packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
+-packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
+-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+-packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
+-packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+-packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+-packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+-packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
+-packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
+-packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+-packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+-packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+-packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+-packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+-packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+-packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+-packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt
+-packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/FeedbackIcon.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/RemoteInputViewModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarIconBlocklist.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyState.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletController.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+-packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
+-packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+-packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
+-packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
+-packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+-packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+-packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+-packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
+-packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
+-packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+-packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+-packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt
+-packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
+-packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
+-packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+-packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
+-packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
+-packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt
+-packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt
+-packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+-packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt
+-packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
+-packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt
+-packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt
+-packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
+-packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+-packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt
+-packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
+-packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+-packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
+-packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+-packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt
+-packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt
+-packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+-packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+-packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
+-packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
+-packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt
+-packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+-packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
+-packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+-packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+-packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt
+-packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+-packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
+-packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+-packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
+-packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
+-packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
+-packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/DeletionJobServiceTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+-packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
+-packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
+-packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
+-packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+-packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+-packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
+-packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+-packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index d886806..ae8e38e 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -16,7 +16,7 @@
 <!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
      is the root view of a window, we cannot change the root view's margins.) -->
 <!-- Alphas start as 0 because the view will be animated in. -->
-<FrameLayout
+<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
             />
 
     </LinearLayout>
-</FrameLayout>
+</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
deleted file mode 100644
index ec00429..0000000
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ /dev/null
@@ -1,102 +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.
-  -->
-
-<com.android.systemui.statusbar.notification.stack.PeopleHubView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/notification_section_header_height"
-    android:paddingStart="4dp"
-    android:paddingEnd="4dp"
-    android:focusable="true"
-    android:clickable="true"
->
-
-    <LinearLayout
-        android:id="@+id/people_list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="8dp"
-        android:gravity="bottom"
-        android:orientation="horizontal"
-        android:forceHasOverlappingRendering="false"
-        android:clipChildren="false">
-
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="start|center_vertical"
-            android:layout_weight="1"
-            android:forceHasOverlappingRendering="false">
-
-            <TextView
-                android:id="@+id/header_label"
-                style="@style/TextAppearance.NotificationSectionHeaderButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/notification_section_header_conversations"
-            />
-
-        </FrameLayout>
-
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="8dp"
-            android:scaleType="fitCenter"
-            android:forceHasOverlappingRendering="false"
-            android:visibility="gone"
-        />
-
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="8dp"
-            android:scaleType="fitCenter"
-            android:forceHasOverlappingRendering="false"
-            android:visibility="gone"
-        />
-
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="8dp"
-            android:scaleType="fitCenter"
-            android:forceHasOverlappingRendering="false"
-            android:visibility="gone"
-        />
-
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="8dp"
-            android:scaleType="fitCenter"
-            android:forceHasOverlappingRendering="false"
-            android:visibility="gone"
-        />
-
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="8dp"
-            android:scaleType="fitCenter"
-            android:forceHasOverlappingRendering="false"
-            android:visibility="gone"
-        />
-
-    </LinearLayout>
-
-</com.android.systemui.statusbar.notification.stack.PeopleHubView>
diff --git a/packages/SystemUI/res/layout/volume_panel_dialog.xml b/packages/SystemUI/res/layout/volume_panel_dialog.xml
new file mode 100644
index 0000000..99a1b5c
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_panel_dialog.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/volume_panel_dialog"
+    android:layout_width="@dimen/large_dialog_width"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/Widget.SliceView.Panel"
+        android:gravity="center_vertical|center_horizontal"
+        android:layout_marginTop="@dimen/dialog_top_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/volume_panel_dialog_title"
+            android:ellipsize="end"
+            android:gravity="center_vertical|center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/sound_settings"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"/>
+    </LinearLayout>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/volume_panel_parent_layout"
+        android:scrollbars="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:minHeight="304dp"
+        android:layout_weight="1"
+        android:overScrollMode="never"/>
+
+    <LinearLayout
+        android:id="@+id/button_layout"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/dialog_button_vertical_padding"
+        android:layout_marginStart="@dimen/dialog_side_padding"
+        android:layout_marginEnd="@dimen/dialog_side_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
+        android:baselineAligned="false"
+        android:clickable="false"
+        android:focusable="false">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="start|center_vertical"
+            android:orientation="vertical">
+            <Button
+                android:id="@+id/settings_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/volume_panel_dialog_settings_button"
+                android:ellipsize="end"
+                android:maxLines="1"
+                style="@style/Widget.Dialog.Button.BorderButton"
+                android:clickable="true"
+                android:focusable="true"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/dialog_button_horizontal_padding"
+            android:layout_gravity="end|center_vertical">
+            <Button
+                android:id="@+id/done_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/inline_done_button"
+                style="@style/Widget.Dialog.Button"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:clickable="true"
+                android:focusable="true"/>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml b/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml
new file mode 100644
index 0000000..d1303ed
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/slice_slider_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <androidx.slice.widget.SliceView
+        android:id="@+id/slice_view"
+        style="@style/Widget.SliceView.Panel.Slider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingVertical="@dimen/volume_panel_slice_vertical_padding"
+        android:paddingHorizontal="@dimen/volume_panel_slice_horizontal_padding"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e34d422..a19145d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -479,6 +479,10 @@
 
     <dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
 
+    <!-- Volume panel slices dimensions -->
+    <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+    <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+
     <!-- Size of each item in the ringer selector drawer. -->
     <dimen name="volume_ringer_drawer_item_size">42dp</dimen>
     <dimen name="volume_ringer_drawer_item_size_half">21dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bfdb170..c7b2ff3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1139,6 +1139,11 @@
     <!-- Content description for accessibility: Hint if click will disable. [CHAR LIMIT=NONE] -->
     <string name="volume_odi_captions_hint_disable">disable</string>
 
+    <!-- Sound and vibration settings dialog title. [CHAR LIMIT=30] -->
+    <string name="sound_settings">Sound &amp; vibration</string>
+    <!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
+    <string name="volume_panel_dialog_settings_button">Settings</string>
+
     <!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
 
     <!-- Screen pinning dialog title. -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f7acf06..6d1bcbb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -935,6 +935,10 @@
         <item name="rowStyle">@style/SliceRow</item>
     </style>
 
+    <style name="Widget.SliceView.Panel.Slider">
+        <item name="rowStyle">@style/SliceRow.Slider</item>
+    </style>
+
     <style name="SliceRow">
         <!-- 2dp start padding for the start icon -->
         <item name="titleItemStartPadding">2dp</item>
@@ -956,6 +960,26 @@
         <item name="actionDividerHeight">32dp</item>
     </style>
 
+    <style name="SliceRow.Slider">
+        <!-- Padding between content and the start icon is 5dp -->
+        <item name="contentStartPadding">5dp</item>
+        <item name="contentEndPadding">0dp</item>
+
+        <!-- 0dp start padding for the end item -->
+        <item name="endItemStartPadding">0dp</item>
+        <!-- 8dp end padding for the end item -->
+        <item name="endItemEndPadding">8dp</item>
+
+        <item name="titleSize">20sp</item>
+        <!-- Align text with slider -->
+        <item name="titleStartPadding">11dp</item>
+        <item name="subContentStartPadding">11dp</item>
+
+        <!-- Padding for indeterminate progress bar -->
+        <item name="progressBarStartPadding">12dp</item>
+        <item name="progressBarEndPadding">16dp</item>
+    </style>
+
     <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">24sp</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e1957c0..93175e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -418,6 +418,7 @@
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state);
 
             getCurrentSecurityController().onResume(reason);
+            updateSideFpsVisibility();
         }
         mView.onResume(
                 mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d752852..98bc28d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -206,7 +206,8 @@
      */
     private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
 
-    private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
+    @VisibleForTesting
+    public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 6c452bd..d718a24 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
 
     fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+    fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
 
     fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt
deleted file mode 100644
index f54bf02..0000000
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt
+++ /dev/null
@@ -1,690 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard.logging
-
-import android.os.RemoteException
-import android.view.WindowManagerPolicyConstants
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
-import com.android.systemui.log.dagger.KeyguardViewMediatorLog
-import javax.inject.Inject
-
-private const val TAG = "KeyguardViewMediatorLog"
-
-@SysUISingleton
-class KeyguardViewMediatorLogger @Inject constructor(
-        @KeyguardViewMediatorLog private val logBuffer: LogBuffer,
-) {
-
-    fun logFailedLoadLockSound(soundPath: String) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                { str1 = soundPath },
-                { "failed to load lock sound from $str1" }
-        )
-    }
-
-    fun logFailedLoadUnlockSound(soundPath: String) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                { str1 = soundPath },
-                { "failed to load unlock sound from $str1" }
-        )
-    }
-
-    fun logFailedLoadTrustedSound(soundPath: String) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                { str1 = soundPath },
-                { "failed to load trusted sound from $str1" }
-        )
-    }
-
-    fun logOnSystemReady() {
-        logBuffer.log(TAG, DEBUG, "onSystemReady")
-    }
-
-    fun logOnStartedGoingToSleep(offReason: Int) {
-        val offReasonString = WindowManagerPolicyConstants.offReasonToString(offReason)
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { str1 = offReasonString },
-                { "onStartedGoingToSleep($str1)" }
-        )
-    }
-
-    fun logPendingExitSecureCallbackCancelled() {
-        logBuffer.log(TAG, DEBUG, "pending exit secure callback cancelled")
-    }
-
-    fun logFailedOnKeyguardExitResultFalse(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onKeyguardExitResult(false)",
-                remoteException
-        )
-    }
-
-    fun logOnFinishedGoingToSleep(offReason: Int) {
-        val offReasonString = WindowManagerPolicyConstants.offReasonToString(offReason)
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { str1 = offReasonString },
-                { "onFinishedGoingToSleep($str1)" }
-        )
-    }
-
-    fun logPinLockRequestedStartingKeyguard() {
-        logBuffer.log(TAG, INFO, "PIN lock requested, starting keyguard")
-    }
-
-    fun logUserSwitching(userId: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { int1 = userId },
-                { "onUserSwitching $int1" }
-        )
-    }
-
-    fun logOnUserSwitchComplete(userId: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { int1 = userId },
-                { "onUserSwitchComplete $int1" }
-        )
-    }
-
-    fun logOnSimStateChanged(subId: Int, slotId: Int, simState: String) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    int1 = subId
-                    int2 = slotId
-                    str1 = simState
-                },
-                { "onSimStateChanged(subId=$int1, slotId=$int2, state=$str1)" }
-        )
-    }
-
-    fun logFailedToCallOnSimSecureStateChanged(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onSimSecureStateChanged",
-                remoteException
-        )
-    }
-
-    fun logIccAbsentIsNotShowing() {
-        logBuffer.log(TAG, DEBUG, "ICC_ABSENT isn't showing, we need to show the " +
-                "keyguard since the device isn't provisioned yet.")
-    }
-
-    fun logSimMovedToAbsent() {
-        logBuffer.log(TAG, DEBUG, "SIM moved to ABSENT when the " +
-                "previous state was locked. Reset the state.")
-    }
-
-    fun logIntentValueIccLocked() {
-        logBuffer.log(TAG, DEBUG, "INTENT_VALUE_ICC_LOCKED and keyguard isn't " +
-                "showing; need to show keyguard so user can enter sim pin")
-    }
-
-    fun logPermDisabledKeyguardNotShowing() {
-        logBuffer.log(TAG, DEBUG, "PERM_DISABLED and keyguard isn't showing.")
-    }
-
-    fun logPermDisabledResetStateLocked() {
-        logBuffer.log(TAG, DEBUG, "PERM_DISABLED, resetStateLocked to show permanently " +
-                "disabled message in lockscreen.")
-    }
-
-    fun logReadyResetState(showing: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = showing },
-                { "READY, reset state? $bool1"}
-        )
-    }
-
-    fun logSimMovedToReady() {
-        logBuffer.log(TAG, DEBUG, "SIM moved to READY when the previously was locked. " +
-                "Reset the state.")
-    }
-
-    fun logUnspecifiedSimState(simState: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { int1 = simState },
-                { "Unspecific state: $int1" }
-        )
-    }
-
-    fun logOccludeLaunchAnimationCancelled(occluded: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = occluded },
-                { "Occlude launch animation cancelled. Occluded state is now: $bool1"}
-        )
-    }
-
-    fun logActivityLaunchAnimatorLaunchContainerChanged() {
-        logBuffer.log(TAG, WTF, "Someone tried to change the launch container for the " +
-                "ActivityLaunchAnimator, which should never happen.")
-    }
-
-    fun logVerifyUnlock() {
-        logBuffer.log(TAG, DEBUG, "verifyUnlock")
-    }
-
-    fun logIgnoreUnlockDeviceNotProvisioned() {
-        logBuffer.log(TAG, DEBUG, "ignoring because device isn't provisioned")
-    }
-
-    fun logFailedToCallOnKeyguardExitResultFalse(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onKeyguardExitResult(false)",
-                remoteException
-        )
-    }
-
-    fun logVerifyUnlockCalledNotExternallyDisabled() {
-        logBuffer.log(TAG, WARNING, "verifyUnlock called when not externally disabled")
-    }
-
-    fun logSetOccluded(isOccluded: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = isOccluded },
-                { "setOccluded($bool1)" }
-        )
-    }
-
-    fun logHandleSetOccluded(isOccluded: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = isOccluded },
-                { "handleSetOccluded($bool1)" }
-        )
-    }
-
-    fun logIgnoreHandleShow() {
-        logBuffer.log(TAG, DEBUG, "ignoring handleShow because system is not ready.")
-    }
-
-    fun logHandleShow() {
-        logBuffer.log(TAG, DEBUG, "handleShow")
-    }
-
-    fun logHandleHide() {
-        logBuffer.log(TAG, DEBUG, "handleHide")
-    }
-
-    fun logSplitSystemUserQuitUnlocking() {
-        logBuffer.log(TAG, DEBUG, "Split system user, quit unlocking.")
-    }
-
-    fun logHandleStartKeyguardExitAnimation(startTime: Long, fadeoutDuration: Long) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    long1 = startTime
-                    long2 = fadeoutDuration
-                },
-                { "handleStartKeyguardExitAnimation startTime=$long1 fadeoutDuration=$long2" }
-        )
-    }
-
-    fun logHandleVerifyUnlock() {
-        logBuffer.log(TAG, DEBUG, "handleVerifyUnlock")
-    }
-
-    fun logHandleNotifyStartedGoingToSleep() {
-        logBuffer.log(TAG, DEBUG, "handleNotifyStartedGoingToSleep")
-    }
-
-    fun logHandleNotifyFinishedGoingToSleep() {
-        logBuffer.log(TAG, DEBUG, "handleNotifyFinishedGoingToSleep")
-    }
-
-    fun logHandleNotifyWakingUp() {
-        logBuffer.log(TAG, DEBUG, "handleNotifyWakingUp")
-    }
-
-    fun logHandleReset() {
-        logBuffer.log(TAG, DEBUG, "handleReset")
-    }
-
-    fun logKeyguardDone() {
-        logBuffer.log(TAG, DEBUG, "keyguardDone")
-    }
-
-    fun logKeyguardDonePending() {
-        logBuffer.log(TAG, DEBUG, "keyguardDonePending")
-    }
-
-    fun logKeyguardGone() {
-        logBuffer.log(TAG, DEBUG, "keyguardGone")
-    }
-
-    fun logUnoccludeAnimationCancelled(isOccluded: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = isOccluded },
-                { "Unocclude animation cancelled. Occluded state is now: $bool1" }
-        )
-    }
-
-    fun logShowLocked() {
-        logBuffer.log(TAG, DEBUG, "showLocked")
-    }
-
-    fun logHideLocked() {
-        logBuffer.log(TAG, DEBUG, "hideLocked")
-    }
-
-    fun logResetStateLocked() {
-        logBuffer.log(TAG, DEBUG, "resetStateLocked")
-    }
-
-    fun logNotifyStartedGoingToSleep() {
-        logBuffer.log(TAG, DEBUG, "notifyStartedGoingToSleep")
-    }
-
-    fun logNotifyFinishedGoingToSleep() {
-        logBuffer.log(TAG, DEBUG, "notifyFinishedGoingToSleep")
-    }
-
-    fun logNotifyStartedWakingUp() {
-        logBuffer.log(TAG, DEBUG, "notifyStartedWakingUp")
-    }
-
-    fun logDoKeyguardShowingLockScreen() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: showing the lock screen")
-    }
-
-    fun logDoKeyguardNotShowingLockScreenOff() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because lockscreen is off")
-    }
-
-    fun logDoKeyguardNotShowingDeviceNotProvisioned() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because device isn't " +
-                "provisioned and the sim is not locked or missing")
-    }
-
-    fun logDoKeyguardNotShowingAlreadyShowing() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because it is already showing")
-    }
-
-    fun logDoKeyguardNotShowingBootingCryptkeeper() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because booting to cryptkeeper")
-    }
-
-    fun logDoKeyguardNotShowingExternallyDisabled() {
-        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because externally disabled")
-    }
-
-    fun logFailedToCallOnDeviceProvisioned(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onDeviceProvisioned",
-                remoteException
-        )
-    }
-
-    fun logMaybeHandlePendingLockNotHandling() {
-        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: not handling because the " +
-                "screen off animation's isKeyguardShowDelayed() returned true. This should be " +
-                "handled soon by #onStartedWakingUp, or by the end actions of the " +
-                "screen off animation.")
-    }
-
-    fun logMaybeHandlePendingLockKeyguardGoingAway() {
-        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: not handling because the " +
-                "keyguard is going away. This should be handled shortly by " +
-                "StatusBar#finishKeyguardFadingAway.")
-    }
-
-    fun logMaybeHandlePendingLockHandling() {
-        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: handling pending lock; " +
-                "locking keyguard.")
-    }
-
-    fun logSetAlarmToTurnOffKeyguard(delayedShowingSequence: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { int1 = delayedShowingSequence },
-                { "setting alarm to turn off keyguard, seq = $int1" }
-        )
-    }
-
-    fun logOnStartedWakingUp(delayedShowingSequence: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { int1 = delayedShowingSequence },
-                { "onStartedWakingUp, seq = $int1" }
-        )
-    }
-
-    fun logSetKeyguardEnabled(enabled: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = enabled },
-                { "setKeyguardEnabled($bool1)" }
-        )
-    }
-
-    fun logIgnoreVerifyUnlockRequest() {
-        logBuffer.log(TAG, DEBUG, "in process of verifyUnlock request, ignoring")
-    }
-
-    fun logRememberToReshowLater() {
-        logBuffer.log(TAG, DEBUG, "remembering to reshow, hiding keyguard, disabling " +
-                "status bar expansion")
-    }
-
-    fun logPreviouslyHiddenReshow() {
-        logBuffer.log(TAG, DEBUG, "previously hidden, reshowing, reenabling status " +
-                "bar expansion")
-    }
-
-    fun logOnKeyguardExitResultFalseResetting() {
-        logBuffer.log(TAG, DEBUG, "onKeyguardExitResult(false), resetting")
-    }
-
-    fun logWaitingUntilKeyguardVisibleIsFalse() {
-        logBuffer.log(TAG, DEBUG, "waiting until mWaitingUntilKeyguardVisible is false")
-    }
-
-    fun logDoneWaitingUntilKeyguardVisible() {
-        logBuffer.log(TAG, DEBUG, "done waiting for mWaitingUntilKeyguardVisible")
-    }
-
-    fun logUnoccludeAnimatorOnAnimationStart() {
-        logBuffer.log(TAG, DEBUG, "UnoccludeAnimator#onAnimationStart. " +
-                "Set occluded = false.")
-    }
-
-    fun logNoAppsProvidedToUnoccludeRunner() {
-        logBuffer.log(TAG, DEBUG, "No apps provided to unocclude runner; " +
-                "skipping animation and unoccluding.")
-    }
-
-    fun logReceivedDelayedKeyguardAction(sequence: Int, delayedShowingSequence: Int) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    int1 = sequence
-                    int2 = delayedShowingSequence
-                },
-                {
-                    "received DELAYED_KEYGUARD_ACTION with seq = $int1 " +
-                            "mDelayedShowingSequence = $int2"
-                }
-        )
-    }
-
-    fun logTimeoutWhileActivityDrawn() {
-        logBuffer.log(TAG, WARNING, "Timeout while waiting for activity drawn")
-    }
-
-    fun logTryKeyguardDonePending(
-            keyguardDonePending: Boolean,
-            hideAnimationRun: Boolean,
-            hideAnimationRunning: Boolean
-    ) {
-        logBuffer.log(TAG, DEBUG,
-                {
-                    bool1 = keyguardDonePending
-                    bool2 = hideAnimationRun
-                    bool3 = hideAnimationRunning
-                },
-                { "tryKeyguardDone: pending - $bool1, animRan - $bool2 animRunning - $bool3" }
-        )
-    }
-
-    fun logTryKeyguardDonePreHideAnimation() {
-        logBuffer.log(TAG, DEBUG, "tryKeyguardDone: starting pre-hide animation")
-    }
-
-    fun logHandleKeyguardDone() {
-        logBuffer.log(TAG, DEBUG, "handleKeyguardDone")
-    }
-
-    fun logDeviceGoingToSleep() {
-        logBuffer.log(TAG, INFO, "Device is going to sleep, aborting keyguardDone")
-    }
-
-    fun logFailedToCallOnKeyguardExitResultTrue(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onKeyguardExitResult(true)",
-                remoteException
-        )
-    }
-
-    fun logHandleKeyguardDoneDrawing() {
-        logBuffer.log(TAG, DEBUG, "handleKeyguardDoneDrawing")
-    }
-
-    fun logHandleKeyguardDoneDrawingNotifyingKeyguardVisible() {
-        logBuffer.log(TAG, DEBUG, "handleKeyguardDoneDrawing: notifying " +
-                "mWaitingUntilKeyguardVisible")
-    }
-
-    fun logUpdateActivityLockScreenState(showing: Boolean, aodShowing: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    bool1 = showing
-                    bool2 = aodShowing
-                },
-                { "updateActivityLockScreenState($bool1, $bool2)" }
-        )
-    }
-
-    fun logFailedToCallSetLockScreenShown(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call setLockScreenShown",
-                remoteException
-        )
-    }
-
-    fun logKeyguardGoingAway() {
-        logBuffer.log(TAG, DEBUG, "keyguardGoingAway")
-    }
-
-    fun logFailedToCallKeyguardGoingAway(keyguardFlag: Int, remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                ERROR,
-                { int1 = keyguardFlag },
-                { "Failed to call keyguardGoingAway($int1)" },
-                remoteException
-        )
-    }
-
-    fun logHideAnimationFinishedRunnable() {
-        logBuffer.log(TAG, WARNING, "mHideAnimationFinishedRunnable#run")
-    }
-
-    fun logFailedToCallOnAnimationFinished(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onAnimationFinished",
-                remoteException
-        )
-    }
-
-    fun logFailedToCallOnAnimationStart(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onAnimationStart",
-                remoteException
-        )
-    }
-
-    fun logOnKeyguardExitRemoteAnimationFinished() {
-        logBuffer.log(TAG, DEBUG, "onKeyguardExitRemoteAnimationFinished")
-    }
-
-    fun logSkipOnKeyguardExitRemoteAnimationFinished(
-            cancelled: Boolean,
-            surfaceBehindRemoteAnimationRunning: Boolean,
-            surfaceBehindRemoteAnimationRequested: Boolean
-    ) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    bool1 = cancelled
-                    bool2 = surfaceBehindRemoteAnimationRunning
-                    bool3 = surfaceBehindRemoteAnimationRequested
-                },
-                {
-                    "skip onKeyguardExitRemoteAnimationFinished cancelled=$bool1 " +
-                            "surfaceAnimationRunning=$bool2 " +
-                            "surfaceAnimationRequested=$bool3"
-                }
-        )
-    }
-
-    fun logOnKeyguardExitRemoteAnimationFinishedHideKeyguardView() {
-        logBuffer.log(TAG, DEBUG, "onKeyguardExitRemoteAnimationFinished" +
-                "#hideKeyguardViewAfterRemoteAnimation")
-    }
-
-    fun logSkipHideKeyguardViewAfterRemoteAnimation(
-            dismissingFromSwipe: Boolean,
-            wasShowing: Boolean
-    ) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    bool1 = dismissingFromSwipe
-                    bool2 = wasShowing
-                },
-                {
-                    "skip hideKeyguardViewAfterRemoteAnimation dismissFromSwipe=$bool1 " +
-                            "wasShowing=$bool2"
-                }
-        )
-    }
-
-    fun logCouldNotGetStatusBarManager() {
-        logBuffer.log(TAG, WARNING, "Could not get status bar manager")
-    }
-
-    fun logAdjustStatusBarLocked(
-            showing: Boolean,
-            occluded: Boolean,
-            secure: Boolean,
-            forceHideHomeRecentsButtons: Boolean,
-            flags: String
-    ) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                {
-                    bool1 = showing
-                    bool2 = occluded
-                    bool3 = secure
-                    bool4 = forceHideHomeRecentsButtons
-                    str3 = flags
-                },
-                {
-                    "adjustStatusBarLocked: mShowing=$bool1 mOccluded=$bool2 isSecure=$bool3 " +
-                            "force=$bool4 --> flags=0x$str3"
-                }
-        )
-    }
-
-    fun logFailedToCallOnShowingStateChanged(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call onShowingStateChanged",
-                remoteException
-        )
-    }
-
-    fun logFailedToCallNotifyTrustedChangedLocked(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call notifyTrustedChangedLocked",
-                remoteException
-        )
-    }
-
-    fun logFailedToCallIKeyguardStateCallback(remoteException: RemoteException) {
-        logBuffer.log(
-                TAG,
-                WARNING,
-                "Failed to call to IKeyguardStateCallback",
-                remoteException
-        )
-    }
-
-    fun logOccludeAnimatorOnAnimationStart() {
-        logBuffer.log(TAG, DEBUG, "OccludeAnimator#onAnimationStart. Set occluded = true.")
-    }
-
-    fun logOccludeAnimationCancelledByWm(isKeyguardOccluded: Boolean) {
-        logBuffer.log(
-                TAG,
-                DEBUG,
-                { bool1 = isKeyguardOccluded },
-                { "Occlude animation cancelled by WM. Setting occluded state to: $bool1" }
-        )
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
new file mode 100644
index 0000000..109be40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -0,0 +1,67 @@
+package com.android.systemui
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FlagListenable
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ChooserSelector @Inject constructor(
+        context: Context,
+        private val featureFlags: FeatureFlags,
+        @Application private val coroutineScope: CoroutineScope,
+        @Background private val bgDispatcher: CoroutineDispatcher
+) : CoreStartable(context) {
+
+    private val packageManager = context.packageManager
+    private val chooserComponent = ComponentName.unflattenFromString(
+            context.resources.getString(ChooserSelectorResourceHelper.CONFIG_CHOOSER_ACTIVITY))
+
+    override fun start() {
+        coroutineScope.launch {
+            val listener = FlagListenable.Listener { event ->
+                if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
+                    launch { updateUnbundledChooserEnabled() }
+                    event.requestNoRestart()
+                }
+            }
+            featureFlags.addListener(Flags.CHOOSER_UNBUNDLED, listener)
+            updateUnbundledChooserEnabled()
+
+            awaitCancellationAndThen { featureFlags.removeListener(listener) }
+        }
+    }
+
+    private suspend fun updateUnbundledChooserEnabled() {
+        setUnbundledChooserEnabled(withContext(bgDispatcher) {
+            featureFlags.isEnabled(Flags.CHOOSER_UNBUNDLED)
+        })
+    }
+
+    private fun setUnbundledChooserEnabled(enabled: Boolean) {
+        val newState = if (enabled) {
+            PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+        } else {
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+        }
+        packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+    }
+
+    suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
+    suspend inline fun awaitCancellationAndThen(block: () -> Unit): Nothing = try {
+        awaitCancellation()
+    } finally {
+        block()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt
rename to packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
index 88e227b..7a2de7b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
@@ -14,10 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger
+package com.android.systemui;
 
-import javax.inject.Qualifier
+import androidx.annotation.StringRes;
 
-/** A [com.android.systemui.log.LogBuffer] for KeyguardViewMediator. */
-@Qualifier
-annotation class KeyguardViewMediatorLog
\ No newline at end of file
+import com.android.internal.R;
+
+/** Helper class for referencing resources */
+class ChooserSelectorResourceHelper {
+
+    private ChooserSelectorResourceHelper() {
+    }
+
+    @StringRes
+    static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 614a87f..0cb4da4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -87,8 +87,6 @@
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -311,8 +309,6 @@
     @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
     @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
-    @Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
-    @Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
     @Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
     @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
@@ -523,8 +519,6 @@
         mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
         mProviders.put(NotificationLockscreenUserManager.class,
                 mNotificationLockscreenUserManager::get);
-        mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
-        mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
         mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
         mProviders.put(NotificationRemoteInputManager.class,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 282f251..6b3f134 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -19,6 +19,9 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -599,7 +602,6 @@
             @Background DelayableExecutor bgExecutor) {
         super(context);
         mExecution = execution;
-        mWakefulnessLifecycle = wakefulnessLifecycle;
         mUserManager = userManager;
         mLockPatternUtils = lockPatternUtils;
         mHandler = handler;
@@ -625,11 +627,24 @@
                     return Unit.INSTANCE;
                 });
 
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
+            @Override
+            public void onFinishedWakingUp() {
+                notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_AWAKE);
+            }
+
+            @Override
+            public void onStartedGoingToSleep() {
+                notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_GOING_TO_SLEEP);
+            }
+        });
+
         mStatusBarStateController = statusBarStateController;
         mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
             @Override
             public void onDozingChanged(boolean isDozing) {
-                notifyDozeChanged(isDozing);
+                notifyDozeChanged(isDozing, wakefulnessLifecycle.getWakefulness());
             }
         });
 
@@ -738,13 +753,16 @@
     @Override
     public void setBiometicContextListener(IBiometricContextListener listener) {
         mBiometricContextListener = listener;
-        notifyDozeChanged(mStatusBarStateController.isDozing());
+        notifyDozeChanged(mStatusBarStateController.isDozing(),
+                mWakefulnessLifecycle.getWakefulness());
     }
 
-    private void notifyDozeChanged(boolean isDozing) {
+    private void notifyDozeChanged(boolean isDozing,
+            @WakefulnessLifecycle.Wakefulness int wakefullness) {
         if (mBiometricContextListener != null) {
             try {
-                mBiometricContextListener.onDozeChanged(isDozing);
+                final boolean isAwake = wakefullness == WAKEFULNESS_AWAKE;
+                mBiometricContextListener.onDozeChanged(isDozing, isAwake);
             } catch (RemoteException e) {
                 Log.w(TAG, "failed to notify initial doze state");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 4fe2dd8..e2ef247 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.shade.NotificationPanelViewController
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -117,7 +116,7 @@
                     )
                 } catch (e: RemoteException) {
                     Log.w(
-                        NotificationPanelViewController.TAG,
+                        "CameraGestureHelper",
                         "Unable to start camera activity",
                         e
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
new file mode 100644
index 0000000..e82d0ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.WindowManager;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.ripple.RippleShader.RippleShape;
+
+/**
+ * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
+ * @hide
+ */
+public class WirelessChargingAnimation {
+    public static final int UNKNOWN_BATTERY_LEVEL = -1;
+    public static final long DURATION = 1500;
+    private static final String TAG = "WirelessChargingView";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final WirelessChargingView mCurrentWirelessChargingView;
+    private static WirelessChargingView mPreviousWirelessChargingView;
+
+    public interface Callback {
+        void onAnimationStarting();
+        void onAnimationEnded();
+    }
+
+    /**
+     * Constructs an empty WirelessChargingAnimation object.  If looper is null,
+     * Looper.myLooper() is used.  Must set
+     * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
+     * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
+     * @hide
+     */
+    private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
+            int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
+            RippleShape rippleShape, UiEventLogger uiEventLogger) {
+        mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
+                transmittingBatteryLevel, batteryLevel, callback, isDozing,
+                rippleShape, uiEventLogger);
+    }
+
+    /**
+     * Creates a wireless charging animation object populated with next view.
+     *
+     * @hide
+     */
+    public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
+            @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
+            Callback callback, boolean isDozing, RippleShape rippleShape,
+            UiEventLogger uiEventLogger) {
+        return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
+                batteryLevel, callback, isDozing, rippleShape, uiEventLogger);
+    }
+
+    /**
+     * Creates a charging animation object using mostly default values for non-dozing and unknown
+     * battery level without charging number shown.
+     */
+    public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel(
+            @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) {
+        return makeWirelessChargingAnimation(context, null,
+                UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false,
+                rippleShape, uiEventLogger);
+    }
+
+    /**
+     * Show the view for the specified duration.
+     */
+    public void show(long delay) {
+        if (mCurrentWirelessChargingView == null ||
+                mCurrentWirelessChargingView.mNextView == null) {
+            throw new RuntimeException("setView must have been called");
+        }
+
+        if (mPreviousWirelessChargingView != null) {
+            mPreviousWirelessChargingView.hide(0);
+        }
+
+        mPreviousWirelessChargingView = mCurrentWirelessChargingView;
+        mCurrentWirelessChargingView.show(delay);
+        mCurrentWirelessChargingView.hide(delay + DURATION);
+    }
+
+    private static class WirelessChargingView {
+        private static final int SHOW = 0;
+        private static final int HIDE = 1;
+
+        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        private final Handler mHandler;
+        private final UiEventLogger mUiEventLogger;
+
+        private int mGravity;
+        private WirelessChargingLayout mView;
+        private WirelessChargingLayout mNextView;
+        private WindowManager mWM;
+        private Callback mCallback;
+
+        public WirelessChargingView(Context context, @Nullable Looper looper,
+                int transmittingBatteryLevel, int batteryLevel, Callback callback,
+                boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) {
+            mCallback = callback;
+            mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
+                    isDozing, rippleShape);
+            mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+            mUiEventLogger = uiEventLogger;
+
+            final WindowManager.LayoutParams params = mParams;
+            params.height = WindowManager.LayoutParams.MATCH_PARENT;
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.format = PixelFormat.TRANSLUCENT;
+            params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+            params.setTitle("Charging Animation");
+            params.layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            params.setFitInsetsTypes(0 /* ignore all system bar insets */);
+            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            params.setTrustedOverlay();
+
+            if (looper == null) {
+                // Use Looper.myLooper() if looper is not specified.
+                looper = Looper.myLooper();
+                if (looper == null) {
+                    throw new RuntimeException(
+                            "Can't display wireless animation on a thread that has not called "
+                                    + "Looper.prepare()");
+                }
+            }
+
+            mHandler = new Handler(looper, null) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case SHOW: {
+                            handleShow();
+                            break;
+                        }
+                        case HIDE: {
+                            handleHide();
+                            // Don't do this in handleHide() because it is also invoked by
+                            // handleShow()
+                            mNextView = null;
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+
+        public void show(long delay) {
+            if (DEBUG) Slog.d(TAG, "SHOW: " + this);
+            mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay);
+        }
+
+        public void hide(long duration) {
+            mHandler.removeMessages(HIDE);
+
+            if (DEBUG) Slog.d(TAG, "HIDE: " + this);
+            mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
+        }
+
+        private void handleShow() {
+            if (DEBUG) {
+                Slog.d(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
+                        + mNextView);
+            }
+
+            if (mView != mNextView) {
+                // remove the old view if necessary
+                handleHide();
+                mView = mNextView;
+                Context context = mView.getContext().getApplicationContext();
+                String packageName = mView.getContext().getOpPackageName();
+                if (context == null) {
+                    context = mView.getContext();
+                }
+                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+                mParams.packageName = packageName;
+                mParams.hideTimeoutMilliseconds = DURATION;
+
+                if (mView.getParent() != null) {
+                    if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this);
+                    mWM.removeView(mView);
+                }
+                if (DEBUG) Slog.d(TAG, "ADD! " + mView + " in " + this);
+
+                try {
+                    if (mCallback != null) {
+                        mCallback.onAnimationStarting();
+                    }
+                    mWM.addView(mView, mParams);
+                    mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED);
+                } catch (WindowManager.BadTokenException e) {
+                    Slog.d(TAG, "Unable to add wireless charging view. " + e);
+                }
+            }
+        }
+
+        private void handleHide() {
+            if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+            if (mView != null) {
+                if (mView.getParent() != null) {
+                    if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this);
+                    if (mCallback != null) {
+                        mCallback.onAnimationEnded();
+                    }
+                    mWM.removeViewImmediate(mView);
+                }
+
+                mView = null;
+            }
+        }
+
+        enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum {
+            @UiEvent(doc = "Wireless charging ripple effect played")
+            WIRELESS_RIPPLE_PLAYED(830);
+
+            private final int mInt;
+            WirelessChargingRippleEvent(int id) {
+                mInt = id;
+            }
+
+            @Override public int getId() {
+                return mInt;
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 6da65d2..0839338 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.charging;
 
-import static com.android.systemui.charging.WirelessChargingRippleControllerKt.DEFAULT_DURATION;
-import static com.android.systemui.charging.WirelessChargingRippleControllerKt.UNKNOWN_BATTERY_LEVEL;
-
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
@@ -44,43 +41,39 @@
 import java.text.NumberFormat;
 
 /**
- * Layout that is used for wireless charging.
- *
- * <p>Wireless charging layout has {@link RippleView} and text view to show the battery level.
+ * @hide
  */
 final class WirelessChargingLayout extends FrameLayout {
+    private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
+    private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
     private static final int SCRIM_COLOR = 0x4C000000;
     private static final int SCRIM_FADE_DURATION = 300;
     private RippleView mRippleView;
 
     WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
-            boolean isDozing, RippleShape rippleShape, long duration) {
+            boolean isDozing, RippleShape rippleShape) {
         super(context);
-        init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape,
-                duration);
+        init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape);
     }
 
     private WirelessChargingLayout(Context context) {
         super(context);
-        init(context, null, /* isDozing= */ false, RippleShape.CIRCLE,
-                DEFAULT_DURATION);
+        init(context, null, /* isDozing= */ false, RippleShape.CIRCLE);
     }
 
     private WirelessChargingLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE,
-                DEFAULT_DURATION);
+        init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE);
     }
 
-    private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape,
-            long duration) {
-        init(c, attrs, -1, -1, isDozing, rippleShape, duration);
+    private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) {
+        init(c, attrs, -1, -1, isDozing, rippleShape);
     }
 
     private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel,
-            int batteryLevel, boolean isDozing, RippleShape rippleShape, long duration) {
+            int batteryLevel, boolean isDozing, RippleShape rippleShape) {
         final boolean showTransmittingBatteryLevel =
-                (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL);
+                (transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL);
 
         // set style based on background
         int style = R.style.ChargingAnim_WallpaperBackground;
@@ -93,7 +86,7 @@
         // amount of battery:
         final TextView percentage = findViewById(R.id.wireless_charging_percentage);
 
-        if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+        if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) {
             percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f));
             percentage.setAlpha(0);
         }
@@ -132,6 +125,7 @@
         // play all animations together
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
+
         ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
                 "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
         scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
@@ -140,21 +134,25 @@
                 "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
         scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
         scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
-        scrimFadeOutAnimator.setStartDelay(duration - SCRIM_FADE_DURATION);
+        scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+                ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+                - SCRIM_FADE_DURATION);
         AnimatorSet animatorSetScrim = new AnimatorSet();
         animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
         animatorSetScrim.start();
 
         mRippleView = findViewById(R.id.wireless_charging_ripple);
         mRippleView.setupShader(rippleShape);
-        mRippleView.setDuration(duration);
-        int color = Utils.getColorAttr(mRippleView.getContext(),
-                android.R.attr.colorAccent).getDefaultColor();
         if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+            mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
             mRippleView.setSparkleStrength(0.22f);
+            int color = Utils.getColorAttr(mRippleView.getContext(),
+                    android.R.attr.colorAccent).getDefaultColor();
             mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28));
         } else {
-            mRippleView.setColor(ColorUtils.setAlphaComponent(color, 45));
+            mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
+            mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+                    android.R.attr.colorAccent).getDefaultColor());
         }
 
         OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt
deleted file mode 100644
index 8b5c7eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.charging
-
-import android.content.Context
-import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.charging.WirelessChargingView.WirelessChargingRippleEvent
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.ChargingLog
-import com.android.systemui.util.concurrency.DelayableExecutor
-import javax.inject.Inject
-
-const val UNKNOWN_BATTERY_LEVEL = -1
-const val DEFAULT_DURATION: Long = 1500
-
-/**
- * Controls the wireless charging animation.
- */
-@SysUISingleton
-class WirelessChargingRippleController @Inject constructor(
-        context: Context,
-        private val uiEventLogger: UiEventLogger,
-        @Main private val delayableExecutor: DelayableExecutor,
-        @ChargingLog private val logBuffer: LogBuffer
-) {
-    private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE)
-            as WindowManager
-
-    @VisibleForTesting
-    var wirelessChargingView: WirelessChargingView? = null
-    private var callback: Callback? = null
-
-    companion object {
-        private const val TAG = "WirelessChargingRippleController"
-    }
-
-    /**
-     * Shows the wireless charging view with the given delay.
-     *
-     * If there's already the animation is playing, the new request will be disregarded.
-     * @param wirelessChargingView WirelessChargingView to display.
-     * @param delay the start delay of the WirelessChargingView.
-     * @param callback optional callback that is triggered on animations start and end.
-     */
-    fun show(wirelessChargingView: WirelessChargingView, delay: Long, callback: Callback? = null) {
-        // Avoid multiple animation getting triggered.
-        if (this.wirelessChargingView != null) {
-            logBuffer.log(TAG, LogLevel.INFO, "Already playing animation, disregard " +
-                    "$wirelessChargingView")
-            return
-        }
-
-        this.wirelessChargingView = wirelessChargingView
-        this.callback = callback
-
-        logBuffer.log(TAG, LogLevel.DEBUG, "SHOW: $wirelessChargingView")
-        delayableExecutor.executeDelayed({ showInternal() }, delay)
-
-        logBuffer.log(TAG, LogLevel.DEBUG, "HIDE: $wirelessChargingView")
-        delayableExecutor.executeDelayed({ hideInternal() }, delay + wirelessChargingView.duration)
-    }
-
-    private fun showInternal() {
-        if (wirelessChargingView == null) {
-            return
-        }
-
-        val chargingLayout = wirelessChargingView!!.getWirelessChargingLayout()
-        try {
-            callback?.onAnimationStarting()
-            windowManager.addView(chargingLayout, wirelessChargingView!!.wmLayoutParams)
-            uiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED)
-        } catch (e: WindowManager.BadTokenException) {
-            logBuffer.log(TAG, LogLevel.ERROR, "Unable to add wireless charging view. $e")
-        }
-    }
-
-    private fun hideInternal() {
-        wirelessChargingView?.getWirelessChargingLayout().let {
-            callback?.onAnimationEnded()
-            if (it?.parent != null) {
-                windowManager.removeViewImmediate(it)
-            }
-        }
-        wirelessChargingView = null
-        callback = null
-    }
-
-    /**
-     * Callbacks that are triggered on animation events.
-     */
-    interface Callback {
-        /** Triggered when the animation starts playing. */
-        fun onAnimationStarting()
-        /** Triggered when the animation ends playing. */
-        fun onAnimationEnded()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt
deleted file mode 100644
index d510cf6..0000000
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.charging
-
-import android.content.Context
-import android.graphics.PixelFormat
-import android.view.View
-import android.view.WindowManager
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger.UiEventEnum
-import com.android.systemui.ripple.RippleShader.RippleShape
-
-/**
- * WirelessChargingView that encapsulates the current and next [WirelessChargingLayout]s.
- */
-class WirelessChargingView (
-        context: Context,
-        transmittingBatteryLevel: Int,
-        batteryLevel: Int,
-        isDozing: Boolean,
-        rippleShape: RippleShape,
-        val duration: Long,
-) {
-    companion object {
-        @JvmStatic
-        fun create(
-                context: Context,
-                transmittingBatteryLevel: Int,
-                batteryLevel: Int,
-                isDozing: Boolean,
-                rippleShape: RippleShape,
-                duration: Long = DEFAULT_DURATION
-        ): WirelessChargingView {
-            return WirelessChargingView(context, transmittingBatteryLevel, batteryLevel, isDozing,
-                    rippleShape, duration)
-        }
-
-        @JvmStatic
-        fun createWithNoBatteryLevel(
-                context: Context,
-                rippleShape: RippleShape,
-                duration: Long = DEFAULT_DURATION
-        ): WirelessChargingView {
-            return create(context,
-                    UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, rippleShape,
-                    duration)
-        }
-    }
-
-    val wmLayoutParams = WindowManager.LayoutParams().apply {
-        height = WindowManager.LayoutParams.MATCH_PARENT
-        width = WindowManager.LayoutParams.MATCH_PARENT
-        format = PixelFormat.TRANSLUCENT
-        type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
-        title = "Charging Animation"
-        layoutInDisplayCutoutMode =
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        fitInsetsTypes = 0
-        flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
-        packageName = context.applicationContext.opPackageName
-        setTrustedOverlay()
-    }
-
-    private val wirelessChargingLayout: WirelessChargingLayout =
-            WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, isDozing,
-                    rippleShape, duration)
-    fun getWirelessChargingLayout(): View = wirelessChargingLayout
-
-    internal enum class WirelessChargingRippleEvent(private val mInt: Int) : UiEventEnum {
-        @UiEvent(doc = "Wireless charging ripple effect played")
-        WIRELESS_RIPPLE_PLAYED(830);
-
-        override fun getId(): Int {
-            return mInt
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 8ba6f1c..d60a222 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -26,6 +26,7 @@
 import com.android.systemui.screenshot.ActionProxyReceiver;
 import com.android.systemui.screenshot.DeleteScreenshotReceiver;
 import com.android.systemui.screenshot.SmartActionsReceiver;
+import com.android.systemui.volume.VolumePanelDialogReceiver;
 
 import dagger.Binds;
 import dagger.Module;
@@ -78,6 +79,15 @@
      */
     @Binds
     @IntoMap
+    @ClassKey(VolumePanelDialogReceiver.class)
+    public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
+            VolumePanelDialogReceiver broadcastReceiver);
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
     @ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
     public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
             PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 6db3e82..8bb27a7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.dagger
 
 import com.android.keyguard.KeyguardBiometricLockoutLogger
+import com.android.systemui.ChooserSelector
 import com.android.systemui.CoreStartable
 import com.android.systemui.LatencyTester
 import com.android.systemui.ScreenDecorations
@@ -60,6 +61,12 @@
     @ClassKey(AuthController::class)
     abstract fun bindAuthController(service: AuthController): CoreStartable
 
+    /** Inject into ChooserCoreStartable. */
+    @Binds
+    @IntoMap
+    @ClassKey(ChooserSelector::class)
+    abstract fun bindChooserSelector(sysui: ChooserSelector): CoreStartable
+
     /** Inject into ClipboardListener.  */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1b060e2..a0ecd22 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -65,7 +65,6 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -225,11 +224,9 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManagerLegacy groupManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            DumpManager dumpManager,
             @Main Executor sysuiMainExecutor) {
         return Optional.ofNullable(BubblesManager.create(context,
                 bubblesOptional,
@@ -243,7 +240,6 @@
                 interruptionStateProvider,
                 zenModeController,
                 notifUserManager,
-                groupManager,
                 notifCollection,
                 notifPipeline,
                 sysUiState,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index 567bdbc..a981f25 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -70,11 +70,7 @@
                 new BcSmartspaceDataPlugin.SmartspaceTargetListener() {
             @Override
             public void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets) {
-                if (!targets.isEmpty()) {
-                    mDreamOverlayStateController.addComplication(mComplication);
-                } else {
-                    mDreamOverlayStateController.removeComplication(mComplication);
-                }
+                mDreamOverlayStateController.addComplication(mComplication);
             }
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 712ca5b..c4553c4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -60,8 +60,8 @@
     public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
             new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
 
-    public static final UnreleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
-            new UnreleasedFlag(109, true);
+    public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
+            new ReleasedFlag(109);
 
     public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD =
             new UnreleasedFlag(110, true);
@@ -252,6 +252,9 @@
     // 1400 - columbus, b/242800729
     public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
 
+    // 1500 - chooser
+    public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 9e2b7c7..a3dc779 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -50,7 +50,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 
@@ -61,6 +60,7 @@
 import java.util.Set;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /** */
 @SysUISingleton
@@ -106,6 +106,8 @@
 
     protected volatile Context mContext;
 
+    private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
+
     private boolean mEnabled;
     private String mKeyboardName;
     private CachedBluetoothDeviceManager mCachedDeviceManager;
@@ -122,8 +124,9 @@
     private int mState;
 
     @Inject
-    public KeyboardUI(Context context) {
+    public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) {
         super(context);
+        this.mBluetoothManagerProvider = bluetoothManagerProvider;
     }
 
     @Override
@@ -181,7 +184,7 @@
             return;
         }
 
-        LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class);
+        LocalBluetoothManager bluetoothManager = mBluetoothManagerProvider.get();
         if (bluetoothManager == null)  {
             if (DEBUG) {
                 Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 70e0d5f..2c60d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -73,6 +73,8 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -104,7 +106,6 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
@@ -512,7 +513,8 @@
         public void onKeyguardVisibilityChanged(boolean showing) {
             synchronized (KeyguardViewMediator.this) {
                 if (!showing && mPendingPinLock) {
-                    mLogger.logPinLockRequestedStartingKeyguard();
+                    Log.i(TAG, "PIN lock requested, starting keyguard");
+
                     // Bring the keyguard back in order to show the PIN lock
                     mPendingPinLock = false;
                     doKeyguardLocked(null);
@@ -522,7 +524,7 @@
 
         @Override
         public void onUserSwitching(int userId) {
-            mLogger.logUserSwitching(userId);
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
             // We need to force a reset of the views, since lockNow (called by
             // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -540,7 +542,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
-            mLogger.logOnUserSwitchComplete(userId);
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
                 // Don't try to dismiss if the user has Pin/Pattern/Password set
@@ -568,7 +570,8 @@
         public void onSimStateChanged(int subId, int slotId, int simState) {
 
             if (DEBUG_SIM_STATES) {
-                mLogger.logOnSimStateChanged(subId, slotId, String.valueOf(simState));
+                Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
+                        + ",state=" + simState + ")");
             }
 
             int size = mKeyguardStateCallbacks.size();
@@ -577,7 +580,7 @@
                 try {
                     mKeyguardStateCallbacks.get(i).onSimSecureStateChanged(simPinSecure);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnSimSecureStateChanged(e);
+                    Slog.w(TAG, "Failed to call onSimSecureStateChanged", e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(i);
                     }
@@ -600,9 +603,9 @@
                     synchronized (KeyguardViewMediator.this) {
                         if (shouldWaitForProvisioning()) {
                             if (!mShowing) {
-                                if (DEBUG_SIM_STATES) {
-                                    mLogger.logIccAbsentIsNotShowing();
-                                }
+                                if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
+                                        + " we need to show the keyguard since the "
+                                        + "device isn't provisioned yet.");
                                 doKeyguardLocked(null);
                             } else {
                                 resetStateLocked();
@@ -612,9 +615,8 @@
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
                             if (lastSimStateWasLocked) {
-                                if (DEBUG_SIM_STATES) {
-                                    mLogger.logSimMovedToAbsent();
-                                }
+                                if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
+                                        + "previous state was locked. Reset the state.");
                                 resetStateLocked();
                             }
                             mSimWasLocked.append(slotId, false);
@@ -627,9 +629,9 @@
                         mSimWasLocked.append(slotId, true);
                         mPendingPinLock = true;
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) {
-                                mLogger.logIntentValueIccLocked();
-                            }
+                            if (DEBUG_SIM_STATES) Log.d(TAG,
+                                    "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+                                    + "showing; need to show keyguard so user can enter sim pin");
                             doKeyguardLocked(null);
                         } else {
                             resetStateLocked();
@@ -639,36 +641,29 @@
                 case TelephonyManager.SIM_STATE_PERM_DISABLED:
                     synchronized (KeyguardViewMediator.this) {
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) {
-                                mLogger.logPermDisabledKeyguardNotShowing();
-                            }
+                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and "
+                                  + "keygaurd isn't showing.");
                             doKeyguardLocked(null);
                         } else {
-                            if (DEBUG_SIM_STATES) {
-                                mLogger.logPermDisabledResetStateLocked();
-                            }
+                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+                                  + "show permanently disabled message in lockscreen.");
                             resetStateLocked();
                         }
                     }
                     break;
                 case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
-                        if (DEBUG_SIM_STATES) {
-                            mLogger.logReadyResetState(mShowing);
-                        }
+                        if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
                         if (mShowing && mSimWasLocked.get(slotId, false)) {
-                            if (DEBUG_SIM_STATES) {
-                                mLogger.logSimMovedToReady();
-                            }
+                            if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
+                                    + "previously was locked. Reset the state.");
                             mSimWasLocked.append(slotId, false);
                             resetStateLocked();
                         }
                     }
                     break;
                 default:
-                    if (DEBUG_SIM_STATES) {
-                        mLogger.logUnspecifiedSimState(simState);
-                    }
+                    if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
                     break;
             }
         }
@@ -713,7 +708,7 @@
             if (targetUserId != ActivityManager.getCurrentUser()) {
                 return;
             }
-            mLogger.logKeyguardDone();
+            if (DEBUG) Log.d(TAG, "keyguardDone");
             tryKeyguardDone();
         }
 
@@ -732,7 +727,7 @@
         @Override
         public void keyguardDonePending(boolean strongAuth, int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
-            mLogger.logKeyguardDonePending();
+            if (DEBUG) Log.d(TAG, "keyguardDonePending");
             if (targetUserId != ActivityManager.getCurrentUser()) {
                 Trace.endSection();
                 return;
@@ -751,7 +746,7 @@
         @Override
         public void keyguardGone() {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardGone");
-            mLogger.logKeyguardGone();
+            if (DEBUG) Log.d(TAG, "keyguardGone");
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
             mKeyguardDisplayManager.hide();
             Trace.endSection();
@@ -837,7 +832,8 @@
 
                 @Override
                 public void onLaunchAnimationCancelled() {
-                    mLogger.logOccludeLaunchAnimationCancelled(mOccluded);
+                    Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+                            + mOccluded);
                 }
 
                 @Override
@@ -857,7 +853,8 @@
                 @Override
                 public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
                     // No-op, launch container is always the shade.
-                    mLogger.logActivityLaunchAnimatorLaunchContainerChanged();
+                    Log.wtf(TAG, "Someone tried to change the launch container for the "
+                            + "ActivityLaunchAnimator, which should never happen.");
                 }
 
                 @NonNull
@@ -908,7 +905,8 @@
                     }
 
                     setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
-                    mLogger.logUnoccludeAnimationCancelled(mOccluded);
+                    Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+                            + mOccluded);
                 }
 
                 @Override
@@ -916,11 +914,12 @@
                         RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                    mLogger.logUnoccludeAnimatorOnAnimationStart();
+                    Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
                     setOccluded(false /* isOccluded */, true /* animate */);
 
                     if (apps == null || apps.length == 0 || apps[0] == null) {
-                        mLogger.logNoAppsProvidedToUnoccludeRunner();
+                        Log.d(TAG, "No apps provided to unocclude runner; "
+                                + "skipping animation and unoccluding.");
                         finishedCallback.onAnimationFinished();
                         return;
                     }
@@ -1008,7 +1007,6 @@
     private ScreenOnCoordinator mScreenOnCoordinator;
 
     private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
-    private KeyguardViewMediatorLogger mLogger;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -1037,8 +1035,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
-            KeyguardViewMediatorLogger logger) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         super(context);
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
@@ -1081,7 +1078,6 @@
         mDreamOverlayStateController = dreamOverlayStateController;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mLogger = logger;
 
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
@@ -1145,21 +1141,21 @@
             mLockSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mLockSoundId == 0) {
-            mLogger.logFailedLoadLockSound(soundPath);
+            Log.w(TAG, "failed to load lock sound from " + soundPath);
         }
         soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND);
         if (soundPath != null) {
             mUnlockSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mUnlockSoundId == 0) {
-            mLogger.logFailedLoadUnlockSound(soundPath);
+            Log.w(TAG, "failed to load unlock sound from " + soundPath);
         }
         soundPath = Settings.Global.getString(cr, Settings.Global.TRUSTED_SOUND);
         if (soundPath != null) {
             mTrustedSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mTrustedSoundId == 0) {
-            mLogger.logFailedLoadTrustedSound(soundPath);
+            Log.w(TAG, "failed to load trusted sound from " + soundPath);
         }
 
         int lockSoundDefaultAttenuation = mContext.getResources().getInteger(
@@ -1188,7 +1184,7 @@
 
     private void handleSystemReady() {
         synchronized (this) {
-            mLogger.logOnSystemReady();
+            if (DEBUG) Log.d(TAG, "onSystemReady");
             mSystemReady = true;
             doKeyguardLocked(null);
             mUpdateMonitor.registerCallback(mUpdateCallback);
@@ -1206,7 +1202,7 @@
      * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
      */
     public void onStartedGoingToSleep(@WindowManagerPolicyConstants.OffReason int offReason) {
-        mLogger.logOnStartedGoingToSleep(offReason);
+        if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + offReason + ")");
         synchronized (this) {
             mDeviceInteractive = false;
             mPowerGestureIntercepted = false;
@@ -1222,11 +1218,11 @@
             long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
             mLockLater = false;
             if (mExitSecureCallback != null) {
-                mLogger.logPendingExitSecureCallbackCancelled();
+                if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
                 try {
                     mExitSecureCallback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    mLogger.logFailedOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
                 mExitSecureCallback = null;
                 if (!mExternallyEnabled) {
@@ -1271,7 +1267,7 @@
      */
     public void onFinishedGoingToSleep(
             @WindowManagerPolicyConstants.OffReason int offReason, boolean cameraGestureTriggered) {
-        mLogger.logOnFinishedGoingToSleep(offReason);
+        if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + offReason + ")");
         synchronized (this) {
             mDeviceInteractive = false;
             mGoingToSleep = false;
@@ -1329,7 +1325,13 @@
             //   - The screen off animation is cancelled by the device waking back up. We will call
             //     maybeHandlePendingLock from KeyguardViewMediator#onStartedWakingUp.
             if (mScreenOffAnimationController.isKeyguardShowDelayed()) {
-                mLogger.logMaybeHandlePendingLockNotHandling();
+                if (DEBUG) {
+                    Log.d(TAG, "#maybeHandlePendingLock: not handling because the screen off "
+                            + "animation's isKeyguardShowDelayed() returned true. This should be "
+                            + "handled soon by #onStartedWakingUp, or by the end actions of the "
+                            + "screen off animation.");
+                }
+
                 return;
             }
 
@@ -1339,11 +1341,18 @@
             // StatusBar#finishKeyguardFadingAway, which is always responsible for setting
             // isKeyguardGoingAway to false.
             if (mKeyguardStateController.isKeyguardGoingAway()) {
-                mLogger.logMaybeHandlePendingLockKeyguardGoingAway();
+                if (DEBUG) {
+                    Log.d(TAG, "#maybeHandlePendingLock: not handling because the keyguard is "
+                            + "going away. This should be handled shortly by "
+                            + "StatusBar#finishKeyguardFadingAway.");
+                }
+
                 return;
             }
 
-            mLogger.logMaybeHandlePendingLockHandling();
+            if (DEBUG) {
+                Log.d(TAG, "#maybeHandlePendingLock: handling pending lock; locking keyguard.");
+            }
 
             doKeyguardLocked(null);
             setPendingLock(false);
@@ -1412,7 +1421,8 @@
         PendingIntent sender = PendingIntent.getBroadcast(mContext,
                 0, intent, PendingIntent.FLAG_CANCEL_CURRENT |  PendingIntent.FLAG_IMMUTABLE);
         mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
-        mLogger.logSetAlarmToTurnOffKeyguard(mDelayedShowingSequence);
+        if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+                         + mDelayedShowingSequence);
         doKeyguardLaterForChildProfilesLocked();
     }
 
@@ -1472,7 +1482,7 @@
             mAnimatingScreenOff = false;
             cancelDoKeyguardLaterLocked();
             cancelDoKeyguardForChildProfilesLocked();
-            mLogger.logOnStartedWakingUp(mDelayedShowingSequence);
+            if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
         mUpdateMonitor.dispatchStartedWakingUp();
@@ -1532,35 +1542,37 @@
      */
     public void setKeyguardEnabled(boolean enabled) {
         synchronized (this) {
-            mLogger.logSetKeyguardEnabled(enabled);
+            if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
 
             mExternallyEnabled = enabled;
 
             if (!enabled && mShowing) {
                 if (mExitSecureCallback != null) {
-                    mLogger.logIgnoreVerifyUnlockRequest();
+                    if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
                     // we're in the process of handling a request to verify the user
                     // can get past the keyguard. ignore extraneous requests to disable / re-enable
                     return;
                 }
 
                 // hiding keyguard that is showing, remember to reshow later
-                mLogger.logRememberToReshowLater();
+                if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+                        + "disabling status bar expansion");
                 mNeedToReshowWhenReenabled = true;
                 updateInputRestrictedLocked();
                 hideLocked();
             } else if (enabled && mNeedToReshowWhenReenabled) {
                 // re-enabled after previously hidden, reshow
-                mLogger.logPreviouslyHiddenReshow();
+                if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+                        + "status bar expansion");
                 mNeedToReshowWhenReenabled = false;
                 updateInputRestrictedLocked();
 
                 if (mExitSecureCallback != null) {
-                    mLogger.logOnKeyguardExitResultFalseResetting();
+                    if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
                     try {
                         mExitSecureCallback.onKeyguardExitResult(false);
                     } catch (RemoteException e) {
-                        mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                        Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                     }
                     mExitSecureCallback = null;
                     resetStateLocked();
@@ -1572,7 +1584,7 @@
                     // and causing an ANR).
                     mWaitingUntilKeyguardVisible = true;
                     mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
-                    mLogger.logWaitingUntilKeyguardVisibleIsFalse();
+                    if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
                     while (mWaitingUntilKeyguardVisible) {
                         try {
                             wait();
@@ -1580,7 +1592,7 @@
                             Thread.currentThread().interrupt();
                         }
                     }
-                    mLogger.logDoneWaitingUntilKeyguardVisible();
+                    if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
                 }
             }
         }
@@ -1592,31 +1604,31 @@
     public void verifyUnlock(IKeyguardExitCallback callback) {
         Trace.beginSection("KeyguardViewMediator#verifyUnlock");
         synchronized (this) {
-            mLogger.logVerifyUnlock();
+            if (DEBUG) Log.d(TAG, "verifyUnlock");
             if (shouldWaitForProvisioning()) {
                 // don't allow this api when the device isn't provisioned
-                mLogger.logIgnoreUnlockDeviceNotProvisioned();
+                if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
             } else if (mExternallyEnabled) {
                 // this only applies when the user has externally disabled the
                 // keyguard.  this is unexpected and means the user is not
                 // using the api properly.
-                mLogger.logVerifyUnlockCalledNotExternallyDisabled();
+                Log.w(TAG, "verifyUnlock called when not externally disabled");
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
             } else if (mExitSecureCallback != null) {
                 // already in progress with someone else
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
             } else if (!isSecure()) {
 
@@ -1628,7 +1640,7 @@
                 try {
                     callback.onKeyguardExitResult(true);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
             } else {
 
@@ -1637,7 +1649,7 @@
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
                 }
             }
         }
@@ -1655,8 +1667,10 @@
      * Notify us when the keyguard is occluded by another window
      */
     public void setOccluded(boolean isOccluded, boolean animate) {
+        Log.d(TAG, "setOccluded(" + isOccluded + ")");
+
         Trace.beginSection("KeyguardViewMediator#setOccluded");
-        mLogger.logSetOccluded(isOccluded);
+        if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
         mHandler.removeMessages(SET_OCCLUDED);
         Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
@@ -1685,7 +1699,7 @@
      */
     private void handleSetOccluded(boolean isOccluded, boolean animate) {
         Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
-        mLogger.logHandleSetOccluded(isOccluded);
+        Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
         synchronized (KeyguardViewMediator.this) {
             if (mHiding && isOccluded) {
                 // We're in the process of going away but WindowManager wants to show a
@@ -1742,7 +1756,7 @@
                 try {
                     callback.onInputRestrictedStateChanged(inputRestricted);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnDeviceProvisioned(e);
+                    Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(callback);
                     }
@@ -1757,7 +1771,8 @@
     private void doKeyguardLocked(Bundle options) {
         // if another app is disabling us, don't show
         if (!mExternallyEnabled) {
-            mLogger.logDoKeyguardNotShowingExternallyDisabled();
+            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
+
             mNeedToReshowWhenReenabled = true;
             return;
         }
@@ -1766,7 +1781,7 @@
         // to account for the hiding animation which results in a delay and discrepancy
         // between flags
         if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) {
-            mLogger.logDoKeyguardNotShowingAlreadyShowing();
+            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
             resetStateLocked();
             return;
         }
@@ -1785,19 +1800,20 @@
                     || ((absent || disabled) && requireSim);
 
             if (!lockedOrMissing && shouldWaitForProvisioning()) {
-                mLogger.logDoKeyguardNotShowingDeviceNotProvisioned();
+                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+                        + " and the sim is not locked or missing");
                 return;
             }
 
             boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
             if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
                     && !lockedOrMissing && !forceShow) {
-                mLogger.logDoKeyguardNotShowingLockScreenOff();
+                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
                 return;
             }
         }
 
-        mLogger.logDoKeyguardShowingLockScreen();
+        if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
         showLocked(options);
     }
 
@@ -1835,23 +1851,32 @@
      * @see #handleReset
      */
     private void resetStateLocked() {
-        mLogger.logResetStateLocked();
+        if (DEBUG) Log.e(TAG, "resetStateLocked");
         Message msg = mHandler.obtainMessage(RESET);
         mHandler.sendMessage(msg);
     }
 
+    /**
+     * Send message to keyguard telling it to verify unlock
+     * @see #handleVerifyUnlock()
+     */
+    private void verifyUnlockLocked() {
+        if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
+        mHandler.sendEmptyMessage(VERIFY_UNLOCK);
+    }
+
     private void notifyStartedGoingToSleep() {
-        mLogger.logNotifyStartedGoingToSleep();
+        if (DEBUG) Log.d(TAG, "notifyStartedGoingToSleep");
         mHandler.sendEmptyMessage(NOTIFY_STARTED_GOING_TO_SLEEP);
     }
 
     private void notifyFinishedGoingToSleep() {
-        mLogger.logNotifyFinishedGoingToSleep();
+        if (DEBUG) Log.d(TAG, "notifyFinishedGoingToSleep");
         mHandler.sendEmptyMessage(NOTIFY_FINISHED_GOING_TO_SLEEP);
     }
 
     private void notifyStartedWakingUp() {
-        mLogger.logNotifyStartedWakingUp();
+        if (DEBUG) Log.d(TAG, "notifyStartedWakingUp");
         mHandler.sendEmptyMessage(NOTIFY_STARTED_WAKING_UP);
     }
 
@@ -1861,7 +1886,7 @@
      */
     private void showLocked(Bundle options) {
         Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock");
-        mLogger.logShowLocked();
+        if (DEBUG) Log.d(TAG, "showLocked");
         // ensure we stay awake until we are finished displaying the keyguard
         mShowKeyguardWakeLock.acquire();
         Message msg = mHandler.obtainMessage(SHOW, options);
@@ -1878,7 +1903,7 @@
      */
     private void hideLocked() {
         Trace.beginSection("KeyguardViewMediator#hideLocked");
-        mLogger.logHideLocked();
+        if (DEBUG) Log.d(TAG, "hideLocked");
         Message msg = mHandler.obtainMessage(HIDE);
         mHandler.sendMessage(msg);
         Trace.endSection();
@@ -1957,7 +1982,8 @@
         public void onReceive(Context context, Intent intent) {
             if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) {
                 final int sequence = intent.getIntExtra("seq", 0);
-                mLogger.logReceivedDelayedKeyguardAction(sequence, mDelayedShowingSequence);
+                if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = "
+                        + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
                 synchronized (KeyguardViewMediator.this) {
                     if (mDelayedShowingSequence == sequence) {
                         doKeyguardLocked(null);
@@ -1990,7 +2016,7 @@
 
     private void keyguardDone() {
         Trace.beginSection("KeyguardViewMediator#keyguardDone");
-        mLogger.logKeyguardDone();
+        if (DEBUG) Log.d(TAG, "keyguardDone()");
         userActivity();
         EventLog.writeEvent(70000, 2);
         Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
@@ -2082,7 +2108,7 @@
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
-                    mLogger.logTimeoutWhileActivityDrawn();
+                    Log.w(TAG, "Timeout while waiting for activity drawn!");
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
@@ -2093,15 +2119,14 @@
     };
 
     private void tryKeyguardDone() {
-        mLogger.logTryKeyguardDonePending(
-                mKeyguardDonePending,
-                mHideAnimationRun,
-                mHideAnimationRunning
-        );
+        if (DEBUG) {
+            Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
+                    + mHideAnimationRun + " animRunning - " + mHideAnimationRunning);
+        }
         if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
             handleKeyguardDone();
         } else if (!mHideAnimationRun) {
-            mLogger.logTryKeyguardDonePreHideAnimation();
+            if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
             mHideAnimationRun = true;
             mHideAnimationRunning = true;
             mKeyguardViewControllerLazy.get()
@@ -2121,14 +2146,14 @@
                 mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
             }
         });
-        mLogger.logHandleKeyguardDone();
+        if (DEBUG) Log.d(TAG, "handleKeyguardDone");
         synchronized (this) {
             resetKeyguardDonePendingLocked();
         }
 
         if (mGoingToSleep) {
             mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
-            mLogger.logDeviceGoingToSleep();
+            Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
             return;
         }
         setPendingLock(false); // user may have authenticated during the screen off animation
@@ -2136,7 +2161,7 @@
             try {
                 mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
             } catch (RemoteException e) {
-                mLogger.logFailedToCallOnKeyguardExitResultTrue(e);
+                Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
             }
 
             mExitSecureCallback = null;
@@ -2179,9 +2204,9 @@
     private void handleKeyguardDoneDrawing() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDoneDrawing");
         synchronized(this) {
-            mLogger.logHandleKeyguardDoneDrawing();
+            if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing");
             if (mWaitingUntilKeyguardVisible) {
-                mLogger.logHandleKeyguardDoneDrawingNotifyingKeyguardVisible();
+                if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible");
                 mWaitingUntilKeyguardVisible = false;
                 notifyAll();
 
@@ -2231,11 +2256,12 @@
 
     private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
         mUiBgExecutor.execute(() -> {
-            mLogger.logUpdateActivityLockScreenState(showing, aodShowing);
+            if (DEBUG) {
+                Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
+            }
             try {
                 ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
-                mLogger.logFailedToCallSetLockScreenShown(e);
             }
         });
     }
@@ -2252,10 +2278,10 @@
         }
         synchronized (KeyguardViewMediator.this) {
             if (!mSystemReady) {
-                mLogger.logIgnoreHandleShow();
+                if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
                 return;
             } else {
-                mLogger.logHandleShow();
+                if (DEBUG) Log.d(TAG, "handleShow");
             }
 
             mHiding = false;
@@ -2287,7 +2313,7 @@
         @Override
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
-            mLogger.logKeyguardGoingAway();
+            if (DEBUG) Log.d(TAG, "keyguardGoingAway");
             mKeyguardViewControllerLazy.get().keyguardGoingAway();
 
             int flags = 0;
@@ -2331,7 +2357,7 @@
                 try {
                     ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallKeyguardGoingAway(keyguardFlag, e);
+                    Log.e(TAG, "Error while calling WindowManager", e);
                 }
             });
             Trace.endSection();
@@ -2339,7 +2365,7 @@
     };
 
     private final Runnable mHideAnimationFinishedRunnable = () -> {
-        mLogger.logHideAnimationFinishedRunnable();
+        Log.e(TAG, "mHideAnimationFinishedRunnable#run");
         mHideAnimationRunning = false;
         tryKeyguardDone();
     };
@@ -2359,14 +2385,14 @@
         }
 
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleHide();
+            if (DEBUG) Log.d(TAG, "handleHide");
 
             if (mustNotUnlockCurrentUser()) {
                 // In split system user mode, we never unlock system user. The end user has to
                 // switch to another user.
                 // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
                 // still completes and makes the screen blank.
-                mLogger.logSplitSystemUserQuitUnlocking();
+                if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
                 mKeyguardExitAnimationRunner = null;
                 return;
             }
@@ -2398,7 +2424,8 @@
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
         Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
-        mLogger.logHandleStartKeyguardExitAnimation(startTime, fadeoutDuration);
+        Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+                + " fadeoutDuration=" + fadeoutDuration);
         synchronized (KeyguardViewMediator.this) {
 
             // Tell ActivityManager that we canceled the keyguard animation if
@@ -2414,7 +2441,7 @@
                     try {
                         finishedCallback.onAnimationFinished();
                     } catch (RemoteException e) {
-                        mLogger.logFailedToCallOnAnimationFinished(e);
+                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
                     }
                 }
                 setShowingLocked(mShowing, true /* force */);
@@ -2437,7 +2464,7 @@
                                 try {
                                     finishedCallback.onAnimationFinished();
                                 } catch (RemoteException e) {
-                                    mLogger.logFailedToCallOnAnimationFinished(e);
+                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2456,7 +2483,7 @@
                     runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
                             wallpapers, nonApps, callback);
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnAnimationStart(e);
+                    Slog.w(TAG, "Failed to call onAnimationStart", e);
                 }
 
             // When remaining on the shade, there's no need to do a fancy remote animation,
@@ -2520,7 +2547,7 @@
                             try {
                                 finishedCallback.onAnimationFinished();
                             } catch (RemoteException e) {
-                                mLogger.logFailedToCallOnAnimationFinished(e);
+                                Slog.e(TAG, "RemoteException");
                             } finally {
                                 mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
@@ -2531,7 +2558,7 @@
                             try {
                                 finishedCallback.onAnimationFinished();
                             } catch (RemoteException e) {
-                                mLogger.logFailedToCallOnAnimationFinished(e);
+                                Slog.e(TAG, "RemoteException");
                             } finally {
                                 mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
@@ -2600,9 +2627,11 @@
      * @param cancelled {@code true} if the animation was cancelled before it finishes.
      */
     public void onKeyguardExitRemoteAnimationFinished(boolean cancelled) {
-        mLogger.logOnKeyguardExitRemoteAnimationFinished();
+        Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
         if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
-            mLogger.logSkipOnKeyguardExitRemoteAnimationFinished(cancelled, false, false);
+            Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled
+                    + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
+                    + " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested);
             return;
         }
 
@@ -2616,13 +2645,13 @@
             onKeyguardExitFinished();
 
             if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
-                mLogger.logOnKeyguardExitRemoteAnimationFinishedHideKeyguardView();
+                Log.d(TAG, "onKeyguardExitRemoteAnimationFinished"
+                        + "#hideKeyguardViewAfterRemoteAnimation");
                 mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
             } else {
-                mLogger.logSkipHideKeyguardViewAfterRemoteAnimation(
-                        mKeyguardStateController.isDismissingFromSwipe(),
-                        wasShowing
-                );
+                Log.d(TAG, "skip hideKeyguardViewAfterRemoteAnimation"
+                        + " dismissFromSwipe=" + mKeyguardStateController.isDismissingFromSwipe()
+                        + " wasShowing=" + wasShowing);
             }
 
             finishSurfaceBehindRemoteAnimation(cancelled);
@@ -2719,7 +2748,7 @@
         }
 
         if (mStatusBarManager == null) {
-            mLogger.logCouldNotGetStatusBarManager();
+            Log.w(TAG, "Could not get status bar manager");
         } else {
             // Disable aspects of the system/status/navigation bars that must not be re-enabled by
             // windows that appear on top, ever
@@ -2737,13 +2766,12 @@
                 }
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
-            mLogger.logAdjustStatusBarLocked(
-                    mShowing,
-                    mOccluded,
-                    isSecure(),
-                    forceHideHomeRecentsButtons,
-                    Integer.toHexString(flags)
-            );
+
+            if (DEBUG) {
+                Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
+                        + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
+                        +  " --> flags=0x" + Integer.toHexString(flags));
+            }
 
             mStatusBarManager.disable(flags);
         }
@@ -2755,7 +2783,7 @@
      */
     private void handleReset() {
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleReset();
+            if (DEBUG) Log.d(TAG, "handleReset");
             mKeyguardViewControllerLazy.get().reset(true /* hideBouncerWhenShowing */);
         }
     }
@@ -2767,7 +2795,7 @@
     private void handleVerifyUnlock() {
         Trace.beginSection("KeyguardViewMediator#handleVerifyUnlock");
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleVerifyUnlock();
+            if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
             setShowingLocked(true);
             mKeyguardViewControllerLazy.get().dismissAndCollapse();
         }
@@ -2776,7 +2804,7 @@
 
     private void handleNotifyStartedGoingToSleep() {
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleNotifyStartedGoingToSleep();
+            if (DEBUG) Log.d(TAG, "handleNotifyStartedGoingToSleep");
             mKeyguardViewControllerLazy.get().onStartedGoingToSleep();
         }
     }
@@ -2787,7 +2815,7 @@
      */
     private void handleNotifyFinishedGoingToSleep() {
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleNotifyFinishedGoingToSleep();
+            if (DEBUG) Log.d(TAG, "handleNotifyFinishedGoingToSleep");
             mKeyguardViewControllerLazy.get().onFinishedGoingToSleep();
         }
     }
@@ -2795,7 +2823,7 @@
     private void handleNotifyStartedWakingUp() {
         Trace.beginSection("KeyguardViewMediator#handleMotifyStartedWakingUp");
         synchronized (KeyguardViewMediator.this) {
-            mLogger.logHandleNotifyWakingUp();
+            if (DEBUG) Log.d(TAG, "handleNotifyWakingUp");
             mKeyguardViewControllerLazy.get().onStartedWakingUp();
         }
         Trace.endSection();
@@ -3061,7 +3089,7 @@
                 try {
                     callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
                 } catch (RemoteException e) {
-                    mLogger.logFailedToCallOnShowingStateChanged(e);
+                    Slog.w(TAG, "Failed to call onShowingStateChanged", e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(callback);
                     }
@@ -3080,7 +3108,7 @@
             try {
                 mKeyguardStateCallbacks.get(i).onTrustedChanged(trusted);
             } catch (RemoteException e) {
-                mLogger.logFailedToCallNotifyTrustedChangedLocked(e);
+                Slog.w(TAG, "Failed to call notifyTrustedChangedLocked", e);
                 if (e instanceof DeadObjectException) {
                     mKeyguardStateCallbacks.remove(i);
                 }
@@ -3103,7 +3131,7 @@
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
                         KeyguardUpdateMonitor.getCurrentUser()));
             } catch (RemoteException e) {
-                mLogger.logFailedToCallIKeyguardStateCallback(e);
+                Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
             }
         }
     }
@@ -3180,7 +3208,7 @@
             // internal state to reflect that immediately, vs. waiting for the launch animator to
             // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
             // be occluded and might re-show the keyguard.
-            mLogger.logOccludeAnimatorOnAnimationStart();
+            Log.d(TAG, "OccludeAnimator#onAnimationStart. Set occluded = true.");
             setOccluded(true /* isOccluded */, false /* animate */);
         }
 
@@ -3188,7 +3216,8 @@
         public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
             super.onAnimationCancelled(isKeyguardOccluded);
 
-            mLogger.logOccludeAnimationCancelledByWm(isKeyguardOccluded);
+            Log.d(TAG, "Occlude animation cancelled by WM. "
+                    + "Setting occluded state to: " + isKeyguardOccluded);
             setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
 
         }
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 fdea62d..56f1ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,7 +30,6 @@
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
-import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -106,8 +105,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
-            KeyguardViewMediatorLogger logger) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         return new KeyguardViewMediator(
                 context,
                 falsingCollector,
@@ -134,8 +132,7 @@
                 interactionJankMonitor,
                 dreamOverlayStateController,
                 notificationShadeWindowController,
-                activityLaunchAnimator,
-                logger);
+                activityLaunchAnimator);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 77ad806..6124e10 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -158,13 +158,8 @@
      * add more detail to every log may do more to improve overall logging than adding more logs
      * with this method.
      */
-    fun log(
-            tag: String,
-            level: LogLevel,
-            @CompileTimeConstant message: String,
-            exception: Throwable? = null
-    ) =
-            log(tag, level, {str1 = message}, { str1!! }, exception)
+    fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
+            log(tag, level, {str1 = message}, { str1!! })
 
     /**
      * You should call [log] instead of this method.
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java
deleted file mode 100644
index 3cfc22c..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.log.dagger;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.android.systemui.log.LogBuffer;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-/**
- * A {@link LogBuffer} for {@link com.android.systemui.charging}
- */
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface ChargingLog {
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index 684839f..323ee21 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,7 +1,4 @@
 package com.android.systemui.log.dagger
 
-import javax.inject.Qualifier
-
 /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
-@Qualifier
 annotation class KeyguardUpdateMonitorLog
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 1562720..c2a8764 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -305,25 +305,4 @@
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
         return factory.create("KeyguardUpdateMonitorLog", 200);
     }
-
-    /**
-     * Provides a {@link LogBuffer} for use by
-     * {@link com.android.systemui.keyguard.KeyguardViewMediator}.
-     */
-    @Provides
-    @SysUISingleton
-    @KeyguardViewMediatorLog
-    public static LogBuffer provideKeyguardViewMediatorLogBuffer(LogBufferFactory factory) {
-        return factory.create("KeyguardViewMediatorLog", 100);
-    }
-
-    /**
-     * Provides a {@link LogBuffer} for use by {@link com.android.systemui.charging}.
-     */
-    @Provides
-    @SysUISingleton
-    @ChargingLog
-    public static LogBuffer provideChargingLogBuffer(LogBufferFactory factory) {
-        return factory.create("ChargingLog", 20);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 012d766..b02393b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -488,8 +488,8 @@
         TextView deviceName = mMediaViewHolder.getSeamlessText();
         final MediaDeviceData device = data.getDevice();
 
-        final boolean enabled;
-        final boolean seamlessDisabled;
+        final boolean isTapEnabled;
+        final boolean useDisabledAlpha;
         final int iconResource;
         CharSequence deviceString;
         if (showBroadcastButton) {
@@ -499,21 +499,25 @@
                     && TextUtils.equals(device.getName(),
                     MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
                             R.string.bt_le_audio_broadcast_dialog_unknown_name)));
-            seamlessDisabled = !mIsCurrentBroadcastedApp;
+            useDisabledAlpha = !mIsCurrentBroadcastedApp;
             // Always be enabled if the broadcast button is shown
-            enabled = true;
+            isTapEnabled = true;
+
+            // Defaults for broadcasting state
             deviceString = mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name);
             iconResource = R.drawable.settings_input_antenna;
         } else {
             // Disable clicking on output switcher for invalid devices and resumption controls
-            seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption();
-            enabled = !seamlessDisabled;
+            useDisabledAlpha = (device != null && !device.getEnabled()) || data.getResumption();
+            isTapEnabled = !useDisabledAlpha;
+
+            // Defaults for non-broadcasting state
             deviceString = mContext.getString(R.string.media_seamless_other_device);
             iconResource = R.drawable.ic_media_home_devices;
         }
 
-        mMediaViewHolder.getSeamlessButton().setAlpha(seamlessDisabled ? DISABLED_ALPHA : 1.0f);
-        seamlessView.setEnabled(enabled);
+        mMediaViewHolder.getSeamlessButton().setAlpha(useDisabledAlpha ? DISABLED_ALPHA : 1.0f);
+        seamlessView.setEnabled(isTapEnabled);
 
         if (device != null) {
             Drawable icon = device.getIcon();
@@ -524,7 +528,9 @@
             } else {
                 iconView.setImageDrawable(icon);
             }
-            deviceString = device.getName();
+            if (device.getName() != null) {
+                deviceString = device.getName();
+            }
         } else {
             // Set to default icon
             iconView.setImageResource(iconResource);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7b497ad..c48271e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -518,9 +518,20 @@
             }
             val actions = createActionsFromState(it.packageName,
                     mediaControllerFactory.create(it.token), UserHandle(it.userId))
-            val data = it.copy(
-                    semanticActions = actions,
-                    isPlaying = isPlayingState(state.state))
+
+            // Control buttons
+            // If flag is enabled and controller has a PlaybackState,
+            // create actions from session info
+            // otherwise, no need to update semantic actions.
+            val data = if (actions != null) {
+                it.copy(
+                        semanticActions = actions,
+                        isPlaying = isPlayingState(state.state))
+            } else {
+                it.copy(
+                        isPlaying = isPlayingState(state.state)
+                )
+            }
             if (DEBUG) Log.d(TAG, "State updated outside of notification")
             onMediaDataLoaded(key, key, data)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 2518659..b3a4ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -348,7 +348,11 @@
 
                 // If we have a controller but get a null route, then don't trust the device
                 val enabled = device != null && (controller == null || route != null)
-                val name = route?.name?.toString() ?: device?.name
+                val name = if (controller == null || route != null) {
+                    route?.name?.toString() ?: device?.name
+                } else {
+                    null
+                }
                 current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
                         id = device?.id, showBroadcastButton = false)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index a153cb6..f93c671 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -26,6 +26,7 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS
+import com.android.systemui.plugins.FalsingManager
 
 /**
  * A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -106,12 +107,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
                 )
@@ -141,12 +145,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
                 )
@@ -212,7 +219,8 @@
         controllerSender: MediaTttChipControllerSender,
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?,
-        uiEventLogger: MediaTttSenderUiEventLogger
+        uiEventLogger: MediaTttSenderUiEventLogger,
+        falsingManager: FalsingManager,
     ): View.OnClickListener? = null
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 9335489..5ad82fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -22,21 +22,25 @@
 import android.os.PowerManager
 import android.util.Log
 import android.view.Gravity
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.TextView
 import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.ChipInfoCommon
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -58,7 +62,9 @@
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
-        private val uiEventLogger: MediaTttSenderUiEventLogger
+        private val uiEventLogger: MediaTttSenderUiEventLogger,
+        private val falsingManager: FalsingManager,
+        private val falsingCollector: FalsingCollector,
 ) : MediaTttChipControllerCommon<ChipSenderInfo>(
         context,
         logger,
@@ -70,6 +76,9 @@
         powerManager,
         R.layout.media_ttt_chip,
 ) {
+
+    private lateinit var parent: MediaTttChipRootView
+
     override val windowLayoutParams = commonWindowLayoutParams.apply {
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
@@ -121,6 +130,15 @@
 
         val chipState = newChipInfo.state
 
+        // Detect falsing touches on the chip.
+        parent = currentChipView as MediaTttChipRootView
+        parent.touchHandler = object : Gefingerpoken {
+            override fun onTouchEvent(ev: MotionEvent?): Boolean {
+                falsingCollector.onTouchEvent(ev)
+                return false
+            }
+        }
+
         // App icon
         val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
 
@@ -136,7 +154,11 @@
         // Undo
         val undoView = currentChipView.requireViewById<View>(R.id.undo)
         val undoClickListener = chipState.undoClickListener(
-                this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
+                this,
+                newChipInfo.routeInfo,
+                newChipInfo.undoCallback,
+                uiEventLogger,
+                falsingManager,
         )
         undoView.setOnClickListener(undoClickListener)
         undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
new file mode 100644
index 0000000..3373159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events on chip. */
+class MediaTttChipRootView(
+        context: Context,
+        attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+    /** Assign this field to observe touch events. */
+    var touchHandler: Gefingerpoken? = null
+
+    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+        touchHandler?.onTouchEvent(ev)
+        return super.dispatchTouchEvent(ev)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 3f93108..5da4809 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -17,7 +17,14 @@
 
 import javax.inject.Inject;
 
-/** */
+/**
+ * Plays a animation to reveal newly added QS tiles.
+ *
+ * The aniumation is played when the user fully opens Quick Settings, and is only shown for
+ * <li> tiles added automatically (not through user customization)
+ * <li> tiles not have been revealed before (memoized via {@code QS_TILE_SPECS_REVEALED}
+ * preference)
+ */
 public class QSTileRevealController {
     private static final long QS_REVEAL_TILES_DELAY = 500L;
 
@@ -39,6 +46,7 @@
             });
         }
     };
+
     QSTileRevealController(Context context, QSPanelController qsPanelController,
             PagedTileLayout pagedTileLayout, QSCustomizerController qsCustomizerController) {
         mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index d2f3a6a..db7c1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -68,7 +68,7 @@
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
         """
@@ -84,7 +84,7 @@
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
         """
@@ -100,7 +100,7 @@
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
         """
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index a918e5d..309059f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.screenshot
 
 import android.graphics.Insets
+import android.util.Log
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
 import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
@@ -61,8 +62,9 @@
         ) {
 
             val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+            Log.d(TAG, "findPrimaryContent: $info")
 
-            result = if (policy.isManagedProfile(info.userId)) {
+            result = if (policy.isManagedProfile(info.user.identifier)) {
                 val image = capture.captureTask(info.taskId)
                     ?: error("Task snapshot returned a null Bitmap!")
 
@@ -70,7 +72,7 @@
                 ScreenshotRequest(
                     TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
                     HardwareBitmapBundler.hardwareBitmapToBundle(image),
-                    info.bounds, Insets.NONE, info.taskId, info.userId, info.component
+                    info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
                 )
             } else {
                 // Create a new request of the same type which includes the top component
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
index 3580010..f73d204 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
@@ -19,6 +19,7 @@
 import android.annotation.UserIdInt
 import android.content.ComponentName
 import android.graphics.Rect
+import android.os.UserHandle
 import android.view.Display
 
 /**
@@ -42,7 +43,7 @@
     data class DisplayContentInfo(
         val component: ComponentName,
         val bounds: Rect,
-        @UserIdInt val userId: Int,
+        val user: UserHandle,
         val taskId: Int,
     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index ba809f6..c2a5060 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -29,9 +29,11 @@
 import android.graphics.Rect
 import android.os.Process
 import android.os.RemoteException
+import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
 import android.view.Display.DEFAULT_DISPLAY
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.infra.ServiceConnector
 import com.android.systemui.SystemUIService
 import com.android.systemui.dagger.SysUISingleton
@@ -45,21 +47,13 @@
 import kotlinx.coroutines.withContext
 
 @SysUISingleton
-internal class ScreenshotPolicyImpl @Inject constructor(
+internal open class ScreenshotPolicyImpl @Inject constructor(
     context: Context,
     private val userMgr: UserManager,
     private val atmService: IActivityTaskManager,
     @Background val bgDispatcher: CoroutineDispatcher,
 ) : ScreenshotPolicy {
 
-    private val systemUiContent =
-        DisplayContentInfo(
-            ComponentName(context, SystemUIService::class.java),
-            Rect(),
-            ActivityTaskManager.INVALID_TASK_ID,
-            Process.myUserHandle().identifier,
-        )
-
     private val proxyConnector: ServiceConnector<IScreenshotProxy> =
         ServiceConnector.Impl(
             context,
@@ -78,6 +72,9 @@
     }
 
     private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+        if (DEBUG) {
+            debugLogRootTaskInfo(info)
+        }
         return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
             info.isVisible &&
             info.isRunning &&
@@ -99,58 +96,46 @@
         }
 
         val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
-        if (DEBUG) {
-            debugLogRootTaskInfos(taskInfoList)
-        }
 
         // If no visible task is located, then report SystemUI as the foreground content
         val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
-
-        val topActivity: ComponentName = target.topActivity ?: error("should not be null")
-        val topChildTask = target.childTaskIds.size - 1
-        val childTaskId = target.childTaskIds[topChildTask]
-        val childTaskUserId = target.childTaskUserIds[topChildTask]
-        val childTaskBounds = target.childTaskBounds[topChildTask]
-
-        return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId)
+        return target.toDisplayContentInfo()
     }
 
-    private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) {
-        for (info in taskInfoList) {
-            Log.d(
-                TAG,
-                "[root task info] " +
-                    "taskId=${info.taskId} " +
-                    "parentTaskId=${info.parentTaskId} " +
-                    "position=${info.position} " +
-                    "positionInParent=${info.positionInParent} " +
-                    "isVisible=${info.isVisible()} " +
-                    "visible=${info.visible} " +
-                    "isFocused=${info.isFocused} " +
-                    "isSleeping=${info.isSleeping} " +
-                    "isRunning=${info.isRunning} " +
-                    "windowMode=${windowingModeToString(info.windowingMode)} " +
-                    "activityType=${activityTypeToString(info.activityType)} " +
-                    "topActivity=${info.topActivity} " +
-                    "topActivityInfo=${info.topActivityInfo} " +
-                    "numActivities=${info.numActivities} " +
-                    "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
-                    "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
-                    "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
-                    "childTaskNames=${Arrays.toString(info.childTaskNames)}"
-            )
+    private fun debugLogRootTaskInfo(info: RootTaskInfo) {
+        Log.d(TAG, "RootTaskInfo={" +
+                "taskId=${info.taskId} " +
+                "parentTaskId=${info.parentTaskId} " +
+                "position=${info.position} " +
+                "positionInParent=${info.positionInParent} " +
+                "isVisible=${info.isVisible()} " +
+                "visible=${info.visible} " +
+                "isFocused=${info.isFocused} " +
+                "isSleeping=${info.isSleeping} " +
+                "isRunning=${info.isRunning} " +
+                "windowMode=${windowingModeToString(info.windowingMode)} " +
+                "activityType=${activityTypeToString(info.activityType)} " +
+                "topActivity=${info.topActivity} " +
+                "topActivityInfo=${info.topActivityInfo} " +
+                "numActivities=${info.numActivities} " +
+                "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
+                "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
+                "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
+                "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
+                "}"
+        )
 
-            for (j in 0 until info.childTaskIds.size) {
-                Log.d(TAG, "    *** [$j] ******")
-                Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
-                Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
-                Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
-                Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
-            }
+        for (j in 0 until info.childTaskIds.size) {
+            Log.d(TAG, "    *** [$j] ******")
+            Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
+            Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
+            Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
+            Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
         }
     }
 
-    private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
+    @VisibleForTesting
+    open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
         withContext(bgDispatcher) {
             try {
                 atmService.getAllRootTaskInfosOnDisplay(displayId)
@@ -160,7 +145,8 @@
             }
         }
 
-    private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+    @VisibleForTesting
+    open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
         proxyConnector
             .postForResult { it.isNotificationShadeExpanded }
             .whenComplete { expanded, error ->
@@ -171,8 +157,30 @@
             }
     }
 
-    companion object {
-        const val TAG: String = "ScreenshotPolicyImpl"
-        const val DEBUG: Boolean = false
-    }
+    @VisibleForTesting
+    internal val systemUiContent =
+        DisplayContentInfo(
+            ComponentName(context, SystemUIService::class.java),
+            Rect(),
+            Process.myUserHandle(),
+            ActivityTaskManager.INVALID_TASK_ID
+        )
+}
+
+private const val TAG: String = "ScreenshotPolicyImpl"
+private const val DEBUG: Boolean = false
+
+@VisibleForTesting
+internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
+    val topActivity: ComponentName = topActivity ?: error("should not be null")
+    val topChildTask = childTaskIds.size - 1
+    val childTaskId = childTaskIds[topChildTask]
+    val childTaskUserId = childTaskUserIds[topChildTask]
+    val childTaskBounds = childTaskBounds[topChildTask]
+
+    return DisplayContentInfo(
+        topActivity,
+        childTaskBounds,
+        UserHandle.of(childTaskUserId),
+        childTaskId)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index 9818af3..1cdacb9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -17,32 +17,30 @@
 package com.android.systemui.shade;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.TapAgainView;
 
-public class NotificationPanelView extends PanelView {
+/** The shade view. */
+public final class NotificationPanelView extends FrameLayout {
+    static final boolean DEBUG = false;
 
-    private static final boolean DEBUG = false;
-
-    /**
-     * Fling expanding QS.
-     */
-    public static final int FLING_EXPAND = 0;
-
-    public static final String COUNTER_PANEL_OPEN = "panel_open";
-    public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+    private final Paint mAlphaPaint = new Paint();
 
     private int mCurrentPanelAlpha;
-    private final Paint mAlphaPaint = new Paint();
     private boolean mDozing;
     private RtlChangeListener mRtlChangeListener;
+    private NotificationPanelViewController.TouchHandler mTouchHandler;
+    private OnConfigurationChangedListener mOnConfigurationChangedListener;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -99,7 +97,36 @@
         return findViewById(R.id.shade_falsing_tap_again);
     }
 
+    /** Sets the touch handler for this view. */
+    public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) {
+        super.setOnTouchListener(touchHandler);
+        mTouchHandler = touchHandler;
+    }
+
+    void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
+        mOnConfigurationChangedListener = listener;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return mTouchHandler.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public void dispatchConfigurationChanged(Configuration newConfig) {
+        super.dispatchConfigurationChanged(newConfig);
+        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+    }
+
+    /** Callback for right-to-left setting changes. */
     interface RtlChangeListener {
+        /** Called when right-to-left setting changes. */
         void onRtlPropertielsChanged(int layoutDirection);
     }
+
+    /** Callback for config changes. */
+    interface OnConfigurationChangedListener {
+        /** Called when configuration changes. */
+        void onConfigurationChanged(Configuration newConfig);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5f07f9f..9a8395c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -33,7 +33,7 @@
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.PanelView.DEBUG;
+import static com.android.systemui.shade.NotificationPanelView.DEBUG;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -248,7 +248,7 @@
 
 @CentralSurfacesComponent.CentralSurfacesScope
 public final class NotificationPanelViewController {
-    public static final String TAG = PanelView.class.getSimpleName();
+    public static final String TAG = "PanelView";
     public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
     public static final float FLING_SPEED_UP_FACTOR = 0.6f;
     public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
@@ -274,7 +274,7 @@
     /**
      * Fling expanding QS.
      */
-    private static final int FLING_EXPAND = 0;
+    public static final int FLING_EXPAND = 0;
 
     /**
      * Fling collapsing QS, potentially stopping when QS becomes QQS.
@@ -430,7 +430,8 @@
     private boolean mQsTracking;
 
     /**
-     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
+     * If set, the ongoing touch gesture might both trigger the expansion in {@link
+     * NotificationPanelView} and
      * the expansion for quick settings.
      */
     private boolean mConflictingQsExpansionGesture;
@@ -5405,13 +5406,13 @@
         return animator;
     }
 
-    /** Update the visibility of {@link PanelView} if necessary. */
+    /** Update the visibility of {@link NotificationPanelView} if necessary. */
     public void updateVisibility() {
         mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
     }
 
     /**
-     * Updates the panel expansion and {@link PanelView} visibility if necessary.
+     * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
      *
      * TODO(b/200063118): Could public calls to this method be replaced with calls to
      *   {@link #updateVisibility()}? That would allow us to make this method private.
@@ -6170,7 +6171,7 @@
     }
 
     public class OnConfigurationChangedListener implements
-            PanelView.OnConfigurationChangedListener {
+            NotificationPanelView.OnConfigurationChangedListener {
         @Override
         public void onConfigurationChanged(Configuration newConfig) {
             loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
deleted file mode 100644
index 4349d81..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-
-public abstract class PanelView extends FrameLayout {
-    public static final boolean DEBUG = false;
-    public static final String TAG = PanelView.class.getSimpleName();
-    private NotificationPanelViewController.TouchHandler mTouchHandler;
-
-    protected CentralSurfaces mCentralSurfaces;
-    protected HeadsUpManagerPhone mHeadsUpManager;
-
-    protected KeyguardBottomAreaView mKeyguardBottomArea;
-    private OnConfigurationChangedListener mOnConfigurationChangedListener;
-
-    public PanelView(Context context) {
-        super(context);
-    }
-
-    public PanelView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public PanelView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) {
-        super.setOnTouchListener(touchHandler);
-        mTouchHandler = touchHandler;
-    }
-
-    public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
-        mOnConfigurationChangedListener = listener;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return mTouchHandler.onInterceptTouchEvent(event);
-    }
-
-    @Override
-    public void dispatchConfigurationChanged(Configuration newConfig) {
-        super.dispatchConfigurationChanged(newConfig);
-        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
-    }
-
-    interface OnConfigurationChangedListener {
-        void onConfigurationChanged(Configuration newConfig);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index b9684fc..3d161d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -24,6 +24,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -73,6 +75,7 @@
     }
 
     @Override
+    @NonNull
     public ExpandableViewState createExpandableViewState() {
         return new EmptyShadeViewState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c983644..87f8a03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -122,6 +122,7 @@
     private static final int MSG_HIDE_TRANSIENT = 1;
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
     private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
+    private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 4;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
     public static final long DEFAULT_HIDE_DELAY_MS =
             3500 + KeyguardIndicationTextView.Y_IN_DURATION;
@@ -188,9 +189,11 @@
                 }
             };
     private ScreenLifecycle mScreenLifecycle;
-    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+    private final ScreenLifecycle.Observer mScreenObserver =
+            new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
+            mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
             if (mBiometricErrorMessageToShowOnScreenOn != null) {
                 showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
                 // We want to keep this message around in case the screen was off
@@ -259,6 +262,8 @@
                     showActionToUnlock();
                 } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
                     hideBiometricMessage();
+                } else if (msg.what == MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON) {
+                    mBiometricErrorMessageToShowOnScreenOn = null;
                 }
             }
         };
@@ -1060,6 +1065,11 @@
             } else if (showActionToUnlock) {
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
+            } else {
+                mBiometricErrorMessageToShowOnScreenOn = helpString;
+                mHandler.sendMessageDelayed(
+                        mHandler.obtainMessage(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON),
+                        1000);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
index 2ca1beb..7b49ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
@@ -1,7 +1,6 @@
 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
@@ -12,14 +11,12 @@
  */
 @SysUISingleton
 class NotificationInteractionTracker @Inject constructor(
-    private val clicker: NotificationClickNotifier,
-    private val entryManager: NotificationEntryManager
+    clicker: NotificationClickNotifier,
 ) : NotifCollectionListener, NotificationInteractionListener {
     private val interactions = mutableMapOf<String, Boolean>()
 
     init {
         clicker.addNotificationInteractionListener(this)
-        entryManager.addCollectionListener(this)
     }
 
     fun hasUserInteractedWith(key: String): Boolean {
@@ -38,5 +35,3 @@
         interactions[key] = true
     }
 }
-
-private const val TAG = "NotificationInteractionTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index d1bc14f..d908243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -50,12 +50,6 @@
     void addUserChangedListener(UserChangedListener listener);
 
     /**
-     * Registers a [KeyguardNotificationSuppressor] that will be consulted during
-     * {@link #shouldShowOnKeyguard(NotificationEntry)}
-     */
-    void addKeyguardNotificationSuppressor(KeyguardNotificationSuppressor suppressor);
-
-    /**
      * Removes a listener previously registered with
      * {@link #addUserChangedListener(UserChangedListener)}
      */
@@ -63,14 +57,8 @@
 
     SparseArray<UserInfo> getCurrentProfiles();
 
-    void setLockscreenPublicMode(boolean isProfilePublic, int userId);
-
     boolean shouldShowLockscreenNotifications();
 
-    boolean shouldHideNotifications(int userId);
-    boolean shouldHideNotifications(String key);
-    boolean shouldShowOnKeyguard(NotificationEntry entry);
-
     boolean isAnyProfilePublicMode();
 
     void updatePublicMode();
@@ -108,11 +96,6 @@
         default void onUserRemoved(int userId) {}
     }
 
-    /** Used to hide notifications on the lockscreen */
-    interface KeyguardNotificationSuppressor {
-        boolean shouldSuppressOnKeyguard(NotificationEntry entry);
-    }
-
     /**
      * Notified when any state pertaining to Notifications has changed; any methods pertaining to
      * notifications should be re-queried.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f4ca7ed..ae5a2c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -15,17 +15,13 @@
  */
 package com.android.systemui.statusbar;
 
-import static android.app.Notification.VISIBILITY_SECRET;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -42,9 +38,10 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -110,7 +107,6 @@
     private LockPatternUtils mLockPatternUtils;
     protected KeyguardManager mKeyguardManager;
     private int mState = StatusBarState.SHADE;
-    private List<KeyguardNotificationSuppressor> mKeyguardSuppressors = new ArrayList<>();
     private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
             new ListenerSet<>();
 
@@ -344,67 +340,6 @@
         }
     }
 
-    /**
-     * Returns true if notifications are temporarily disabled for this user for security reasons,
-     * regardless of the normal settings for that user.
-     */
-    private boolean shouldTemporarilyHideNotifications(int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            userId = mCurrentUserId;
-        }
-        boolean inLockdown = Dependency.get(KeyguardUpdateMonitor.class).isUserInLockdown(userId);
-        mUsersInLockdownLatestResult.put(userId, inLockdown);
-        return inLockdown;
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
-     * If so, notifications should be hidden.
-     */
-    public boolean shouldHideNotifications(int userId) {
-        boolean hide = isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
-                || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId))
-                || shouldTemporarilyHideNotifications(userId);
-        mShouldHideNotifsLatestResult.put(userId, hide);
-        return hide;
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
-     * package-specific override.
-     */
-    public boolean shouldHideNotifications(String key) {
-        if (mCommonNotifCollectionLazy.get() == null) {
-            Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
-            return true;
-        }
-        NotificationEntry visibleEntry = mCommonNotifCollectionLazy.get().getEntry(key);
-        return isLockscreenPublicMode(mCurrentUserId) && visibleEntry != null
-                && visibleEntry.getRanking().getLockscreenVisibilityOverride() == VISIBILITY_SECRET;
-    }
-
-    public boolean shouldShowOnKeyguard(NotificationEntry entry) {
-        if (mCommonNotifCollectionLazy.get() == null) {
-            Log.wtf(TAG, "mCommonNotifCollectionLazy was null!", new Throwable());
-            return false;
-        }
-        for (int i = 0; i < mKeyguardSuppressors.size(); i++) {
-            if (mKeyguardSuppressors.get(i).shouldSuppressOnKeyguard(entry)) {
-                return false;
-            }
-        }
-        boolean exceedsPriorityThreshold;
-        if (mHideSilentNotificationsOnLockscreen) {
-            exceedsPriorityThreshold =
-                    entry.getBucket() == BUCKET_MEDIA_CONTROLS
-                            || (entry.getBucket() != BUCKET_SILENT
-                            && entry.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT);
-        } else {
-            exceedsPriorityThreshold = !entry.getRanking().isAmbient();
-        }
-        return mShowLockscreenNotifications && exceedsPriorityThreshold;
-    }
-
     private void setShowLockscreenNotifications(boolean show) {
         mShowLockscreenNotifications = show;
     }
@@ -491,7 +426,8 @@
     /**
      * Save the current "public" (locked and secure) state of the lockscreen.
      */
-    public void setLockscreenPublicMode(boolean publicMode, int userId) {
+    @VisibleForTesting
+    void setLockscreenPublicMode(boolean publicMode, int userId) {
         mLockscreenPublicMode.put(userId, publicMode);
     }
 
@@ -674,11 +610,6 @@
     }
 
     @Override
-    public void addKeyguardNotificationSuppressor(KeyguardNotificationSuppressor suppressor) {
-        mKeyguardSuppressors.add(suppressor);
-    }
-
-    @Override
     public void removeUserChangedListener(UserChangedListener listener) {
         mListeners.remove(listener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 2214287..cea3deb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -29,6 +29,8 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
@@ -153,6 +155,7 @@
     }
 
     @Override
+    @NonNull
     public ExpandableViewState createExpandableViewState() {
         return new ShelfState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
index 7097568..e582a01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
@@ -31,7 +31,7 @@
             com.android.internal.R.drawable.ic_wifi_signal_4
     };
 
-    private static final int[] WIFI_NO_INTERNET_ICONS = {
+    public static final int[] WIFI_NO_INTERNET_ICONS = {
             R.drawable.ic_no_internet_wifi_signal_0,
             R.drawable.ic_no_internet_wifi_signal_1,
             R.drawable.ic_no_internet_wifi_signal_2,
@@ -48,7 +48,7 @@
 
     public static final int QS_WIFI_DISABLED = com.android.internal.R.drawable.ic_wifi_signal_0;
     public static final int QS_WIFI_NO_NETWORK = com.android.internal.R.drawable.ic_wifi_signal_0;
-    static final int WIFI_NO_NETWORK = QS_WIFI_NO_NETWORK;
+    public static final int WIFI_NO_NETWORK = QS_WIFI_NO_NETWORK;
 
     static final int WIFI_LEVEL_COUNT = WIFI_SIGNAL_STRENGTH[0].length;
 
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 8cb18a0..59022c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -50,7 +50,6 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -76,20 +75,22 @@
     private final Executor mUiBgExecutor;
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
     private final CommandQueue mCommandQueue;
-    private KeyguardStateController mKeyguardStateController;
+    private final KeyguardStateController mKeyguardStateController;
 
     @Inject
-    public InstantAppNotifier(Context context, CommandQueue commandQueue,
-            @UiBackground Executor uiBgExecutor) {
+    public InstantAppNotifier(
+            Context context,
+            CommandQueue commandQueue,
+            @UiBackground Executor uiBgExecutor,
+            KeyguardStateController keyguardStateController) {
         super(context);
         mCommandQueue = commandQueue;
         mUiBgExecutor = uiBgExecutor;
+        mKeyguardStateController = keyguardStateController;
     }
 
     @Override
     public void start() {
-        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
-
         // listen for user / profile change.
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
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 e2f87b6..54be9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -23,7 +23,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 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.notification.dagger.NotificationsModule;
 
@@ -47,7 +46,7 @@
  * user. After an entry makes its way into the active state, we sort and filter the entire set to
  * repopulate the visible set.
  */
-public class NotificationEntryManager implements VisualStabilityManager.Callback {
+public class NotificationEntryManager {
 
     private final NotificationEntryManagerLogger mLogger;
 
@@ -85,11 +84,6 @@
         mNotificationEntryListeners.remove(listener);
     }
 
-    @Override
-    public void onChangeAllowed() {
-        updateNotifications("reordering is now allowed");
-    }
-
     /**
      * Update the notifications
      * @param reason why the notifications are updating
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
deleted file mode 100644
index c9c6f28..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
+++ /dev/null
@@ -1,77 +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.statusbar.notification;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-
-import java.util.Objects;
-
-/**
- * Root controller for the list of notifications in the shade.
- *
- * TODO: Much of the code in NotificationPresenter should eventually move in here. It will proxy
- * domain-specific behavior (ARC, etc) to subcontrollers.
- */
-public class NotificationListController {
-    private final NotificationEntryManager mEntryManager;
-    private final NotificationListContainer mListContainer;
-    private final DeviceProvisionedController mDeviceProvisionedController;
-
-    public NotificationListController(
-            NotificationEntryManager entryManager,
-            NotificationListContainer listContainer,
-            DeviceProvisionedController deviceProvisionedController) {
-        mEntryManager = Objects.requireNonNull(entryManager);
-        mListContainer = Objects.requireNonNull(listContainer);
-        mDeviceProvisionedController = Objects.requireNonNull(deviceProvisionedController);
-    }
-
-    /**
-     * Causes the controller to register listeners on its dependencies. This method must be called
-     * before the controller is ready to perform its duties.
-     */
-    public void bind() {
-        mEntryManager.addNotificationEntryListener(mEntryListener);
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-    }
-
-    @SuppressWarnings("FieldCanBeLocal")
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
-        @Override
-        public void onEntryRemoved(
-                NotificationEntry entry,
-                NotificationVisibility visibility,
-                boolean removedByUser,
-                int reason) {
-            mListContainer.cleanUpViewStateForEntry(entry);
-        }
-    };
-
-    // TODO: (b/145659174) remove after moving to NewNotifPipeline. Replaced by
-    //  DeviceProvisionedCoordinator
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    mEntryManager.updateNotifications("device provisioned changed");
-                }
-            };
-}
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 dbf4810..126a986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,10 +18,8 @@
 
 import android.animation.ObjectAnimator
 import android.util.FloatProperty
-import com.android.systemui.Dumpable
 import com.android.systemui.animation.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.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -34,20 +32,17 @@
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.min
 
 @SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
-    dumpManager: DumpManager,
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
     private val bypassController: KeyguardBypassController,
     private val dozeParameters: DozeParameters,
     private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener,
-    Dumpable {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
 
     private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
         "notificationVisibility") {
@@ -65,7 +60,6 @@
 
     private var mLinearDozeAmount: Float = 0.0f
     private var mDozeAmount: Float = 0.0f
-    private var mDozeAmountSource: String = "init"
     private var mNotificationVisibleAmount = 0.0f
     private var mNotificationsVisible = false
     private var mNotificationsVisibleForExpansion = false
@@ -148,7 +142,6 @@
         }
 
     init {
-        dumpManager.registerDumpable(this)
         mHeadsUpManager.addListener(this)
         statusBarStateController.addCallback(this)
         addListener(object : WakeUpListener {
@@ -255,14 +248,13 @@
             // Let's notify the scroller that an animation started
             notifyAnimationStart(mLinearDozeAmount == 1.0f)
         }
-        setDozeAmount(linear, eased, source = "StatusBar")
+        setDozeAmount(linear, eased)
     }
 
-    fun setDozeAmount(linear: Float, eased: Float, source: String) {
+    fun setDozeAmount(linear: Float, eased: Float) {
         val changed = linear != mLinearDozeAmount
         mLinearDozeAmount = linear
         mDozeAmount = eased
-        mDozeAmountSource = source
         mStackScrollerController.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
@@ -279,7 +271,7 @@
             // undefined state, so it's an indication that we should do state cleanup. We override
             // the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
             // See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
-            setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
+            setDozeAmount(0f, 0f)
         }
 
         if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -319,11 +311,12 @@
      */
     private fun overrideDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
-            if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
-            } else {
-                setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
+            var amount = 1.0f
+            if (statusBarStateController.state == StatusBarState.SHADE ||
+                statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
+                amount = 0.0f
             }
+            setDozeAmount(amount, amount)
             return true
         }
         return false
@@ -339,7 +332,7 @@
      */
     private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
         if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
-            setDozeAmount(1f, 1f, source = "Override: animating screen off")
+            setDozeAmount(1f, 1f)
             return true
         }
 
@@ -433,24 +426,4 @@
          */
         @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
     }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("mLinearDozeAmount: $mLinearDozeAmount")
-        pw.println("mDozeAmount: $mDozeAmount")
-        pw.println("mDozeAmountSource: $mDozeAmountSource")
-        pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
-        pw.println("mNotificationsVisible: $mNotificationsVisible")
-        pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
-        pw.println("mVisibilityAmount: $mVisibilityAmount")
-        pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
-        pw.println("pulseExpanding: $pulseExpanding")
-        pw.println("state: ${StatusBarState.toString(state)}")
-        pw.println("fullyAwake: $fullyAwake")
-        pw.println("wakingUp: $wakingUp")
-        pw.println("willWakeUp: $willWakeUp")
-        pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
-        pw.println("pulsing: $pulsing")
-        pw.println("notificationsFullyHidden: $notificationsFullyHidden")
-        pw.println("canShowPulsingHuns: $canShowPulsingHuns")
-    }
-}
+}
\ No newline at end of file
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 68bf69a..2887f97 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
@@ -88,9 +88,9 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionInconsistencyTracker;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLoggerKt;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -112,7 +112,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
@@ -151,6 +150,7 @@
     private final Executor mBgExecutor;
     private final LogBufferEulogizer mEulogizer;
     private final DumpManager mDumpManager;
+    private final NotifCollectionInconsistencyTracker mInconsistencyTracker;
 
     private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
     private final Collection<NotificationEntry> mReadOnlyNotificationSet =
@@ -162,7 +162,6 @@
     private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
     private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
-    private Set<String> mNotificationsWithoutRankings = Collections.emptySet();
 
     private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
 
@@ -188,6 +187,7 @@
         mBgExecutor = bgExecutor;
         mEulogizer = logBufferEulogizer;
         mDumpManager = dumpManager;
+        mInconsistencyTracker = new NotifCollectionInconsistencyTracker(mLogger);
     }
 
     /** Initializes the NotifCollection and registers it to receive notification events. */
@@ -199,6 +199,7 @@
         mAttached = true;
         mDumpManager.registerDumpable(TAG, this);
         groupCoalescer.setNotificationHandler(mNotifHandler);
+        mInconsistencyTracker.attach(mNotificationSet::keySet, groupCoalescer::getCoalescedKeySet);
     }
 
     /**
@@ -598,14 +599,9 @@
                 }
             }
         }
-        NotifCollectionLoggerKt.maybeLogInconsistentRankings(
-                mLogger,
-                mNotificationsWithoutRankings,
-                currentEntriesWithoutRankings,
-                rankingMap
-        );
-        mNotificationsWithoutRankings = currentEntriesWithoutRankings == null
-                ? Collections.emptySet() : currentEntriesWithoutRankings.keySet();
+
+        mInconsistencyTracker.logNewMissingNotifications(rankingMap);
+        mInconsistencyTracker.logNewInconsistentRankings(currentEntriesWithoutRankings, rankingMap);
         if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
             for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
                 entry.mCancellationReason = REASON_UNKNOWN;
@@ -864,10 +860,7 @@
                         true,
                         "\t\t"));
 
-        pw.println("\n\tmNotificationsWithoutRankings: " + mNotificationsWithoutRankings.size());
-        for (String key : mNotificationsWithoutRankings) {
-            pw.println("\t * : " + key);
-        }
+        mInconsistencyTracker.dump(pw);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 98f2167..96b3542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -39,9 +39,11 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -118,6 +120,11 @@
         mHandler = handler;
     }
 
+    /** @return the set of notification keys currently in the coalescer */
+    public Set<String> getCoalescedKeySet() {
+        return Collections.unmodifiableSet(mCoalescedEvents.keySet());
+    }
+
     private final NotificationHandler mListener = new NotificationHandler() {
         @Override
         public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
index 46b467e..d52f3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 
-import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -35,23 +34,11 @@
      */
     void inflateViews(
             NotificationEntry entry,
-            NotifInflater.Params params,
+            @NonNull NotifInflater.Params params,
             NotificationRowContentBinder.InflationCallback callback)
             throws InflationException;
 
     /**
-     * Called when the ranking has been updated (but not add or remove has been done). The binder
-     * should inspect the old and new adjustments and re-inflate the entry's views if necessary
-     * (e.g. if something important changed).
-     */
-    void onNotificationRankingUpdated(
-            NotificationEntry entry,
-            @Nullable Integer oldImportance,
-            NotificationUiAdjustment oldAdjustment,
-            NotificationUiAdjustment newAdjustment,
-            NotificationRowContentBinder.InflationCallback callback);
-
-    /**
      * Called when a notification is no longer likely to be displayed and can have its views freed.
      */
     void releaseViews(NotificationEntry entry);
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 528f720..47cdde4 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
@@ -22,6 +22,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Build;
@@ -32,12 +33,9 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationClicker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
@@ -68,8 +66,6 @@
     private final ExpandableNotificationRowComponent.Builder
             mExpandableNotificationRowComponentBuilder;
     private final IconManager mIconManager;
-    private final LowPriorityInflationHelper mLowPriorityInflationHelper;
-    private final NotifPipelineFlags mNotifPipelineFlags;
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
@@ -86,9 +82,7 @@
             RowContentBindStage rowContentBindStage,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
-            IconManager iconManager,
-            LowPriorityInflationHelper lowPriorityInflationHelper,
-            NotifPipelineFlags notifPipelineFlags) {
+            IconManager iconManager) {
         mContext = context;
         mNotifBindPipeline = notifBindPipeline;
         mRowContentBindStage = rowContentBindStage;
@@ -98,8 +92,6 @@
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
         mIconManager = iconManager;
-        mLowPriorityInflationHelper = lowPriorityInflationHelper;
-        mNotifPipelineFlags = notifPipelineFlags;
     }
 
     /**
@@ -125,13 +117,9 @@
     @Override
     public void inflateViews(
             NotificationEntry entry,
-            NotifInflater.Params params,
+            @NonNull NotifInflater.Params params,
             NotificationRowContentBinder.InflationCallback callback)
             throws InflationException {
-        if (params == null) {
-            // weak assert that the params should always be passed in the new pipeline
-            mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        }
         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
 
         if (entry.rowExists()) {
@@ -191,39 +179,6 @@
     }
 
     /**
-     * Updates the views bound to an entry when the entry's ranking changes, either in-place or by
-     * reinflating them.
-     *
-     * TODO: Should this method be in this class?
-     */
-    @Override
-    public void onNotificationRankingUpdated(
-            NotificationEntry entry,
-            @Nullable Integer oldImportance,
-            NotificationUiAdjustment oldAdjustment,
-            NotificationUiAdjustment newAdjustment,
-            NotificationRowContentBinder.InflationCallback callback) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
-            if (entry.rowExists()) {
-                ExpandableNotificationRow row = entry.getRow();
-                row.reset();
-                updateRow(entry, row);
-                inflateContentViews(entry, null, row, callback);
-            } else {
-                // Once the RowInflaterTask is done, it will pick up the updated entry, so
-                // no-op here.
-            }
-        } else {
-            if (oldImportance != null && entry.getImportance() != oldImportance) {
-                if (entry.rowExists()) {
-                    entry.getRow().onNotificationRankingUpdated();
-                }
-            }
-        }
-    }
-
-    /**
      * Update row after the notification has updated.
      *
      * @param entry notification that has updated
@@ -243,24 +198,12 @@
      */
     private void inflateContentViews(
             NotificationEntry entry,
-            NotifInflater.Params inflaterParams,
+            @NonNull NotifInflater.Params inflaterParams,
             ExpandableNotificationRow row,
             @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
         final boolean useIncreasedCollapsedHeight =
                 mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
-        final boolean isLowPriority;
-        if (inflaterParams != null) {
-            // NEW pipeline
-            isLowPriority = inflaterParams.isLowPriority();
-        } else {
-            // LEGACY pipeline
-            mNotifPipelineFlags.checkLegacyPipelineEnabled();
-            // If this is our first time inflating, we don't actually know the groupings for real
-            // yet, so we might actually inflate a low priority content view incorrectly here and
-            // have to correct it later in the pipeline. On subsequent inflations (i.e. updates),
-            // this should inflate the correct view.
-            isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
-        }
+        final boolean isLowPriority = inflaterParams.isLowPriority();
 
         RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
         params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java
deleted file mode 100644
index 89445a5..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import javax.inject.Inject;
-
-/**
- * Helper class that provide methods to help check when we need to inflate a low priority version
- * ot notification content.
- */
-@SysUISingleton
-public class LowPriorityInflationHelper {
-    private final NotificationGroupManagerLegacy mGroupManager;
-    private final NotifPipelineFlags mNotifPipelineFlags;
-
-    @Inject
-    LowPriorityInflationHelper(
-            NotificationGroupManagerLegacy groupManager,
-            NotifPipelineFlags notifPipelineFlags) {
-        mGroupManager = groupManager;
-        mNotifPipelineFlags = notifPipelineFlags;
-    }
-
-    /**
-     * Whether the notification should inflate a low priority version of its content views.
-     */
-    public boolean shouldUseLowPriorityView(NotificationEntry entry) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return entry.isAmbient() && !mGroupManager.isChildInGroup(entry);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
deleted file mode 100644
index d41f6fe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ /dev/null
@@ -1,950 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-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.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.util.Compile;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TreeSet;
-import java.util.function.Function;
-
-import javax.inject.Inject;
-
-import dagger.Lazy;
-
-/**
- * A class to handle notifications and their corresponding groups.
- * This includes:
- * 1. Determining whether an entry is a member of a group and whether it is a summary or a child
- * 2. Tracking group expansion states
- */
-@SysUISingleton
-public class NotificationGroupManagerLegacy implements StateListener, Dumpable {
-
-    private static final String TAG = "LegacyNotifGroupManager";
-    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
-    /**
-     * The maximum amount of time (in ms) between the posting of notifications that can be
-     * considered part of the same update batch.
-     */
-    private static final long POST_BATCH_MAX_AGE = 5000;
-    private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
-    private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
-    private final Optional<Bubbles> mBubblesOptional;
-    private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get);
-    private final HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
-    private boolean mIsUpdatingUnchangedGroup;
-
-    @Inject
-    public NotificationGroupManagerLegacy(
-            StatusBarStateController statusBarStateController,
-            Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
-            Optional<Bubbles> bubblesOptional,
-            DumpManager dumpManager) {
-        statusBarStateController.addCallback(this);
-        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
-        mBubblesOptional = bubblesOptional;
-
-        dumpManager.registerDumpable(this);
-    }
-
-    /**
-     * Add a listener for changes to groups.
-     */
-    public void registerGroupChangeListener(OnGroupChangeListener listener) {
-        mEventDispatcher.registerGroupChangeListener(listener);
-    }
-
-    private void setGroupExpanded(NotificationGroup group, boolean expanded) {
-        group.expanded = expanded;
-    }
-
-    /**
-     * When we want to remove an entry from being tracked for grouping
-     */
-    public void onEntryRemoved(NotificationEntry removed) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryRemoved: entry=" + logKey(removed));
-        }
-        mEventDispatcher.openBufferScope();
-        onEntryRemovedInternal(removed, removed.getSbn());
-        StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey());
-        if (oldSbn != null) {
-            updateSuppression(mGroupMap.get(oldSbn.getGroupKey()));
-        }
-        mEventDispatcher.closeBufferScope();
-    }
-
-    /**
-     * An entry was removed.
-     *
-     * @param removed the removed entry
-     * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
-     *            notification
-     */
-    private void onEntryRemovedInternal(NotificationEntry removed,
-            final StatusBarNotification sbn) {
-        onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
-                sbn.getNotification().isGroupSummary());
-    }
-
-    private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
-            isGroup, boolean isGroupSummary) {
-        String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
-        final NotificationGroup group = mGroupMap.get(groupKey);
-        if (group == null) {
-            // When an app posts 2 different notifications as summary of the same group, then a
-            // cancellation of the first notification removes this group.
-            // This situation is not supported and we will not allow such notifications anymore in
-            // the close future. See b/23676310 for reference.
-            return;
-        }
-        if (SPEW) {
-            Log.d(TAG, "onEntryRemovedInternal: entry=" + logKey(removed)
-                    + " group=" + logGroupKey(group));
-        }
-        if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
-            group.children.remove(removed.getKey());
-        } else {
-            group.summary = null;
-        }
-        updateSuppression(group);
-        if (group.children.isEmpty()) {
-            if (group.summary == null) {
-                mGroupMap.remove(groupKey);
-                mEventDispatcher.notifyGroupRemoved(group);
-            }
-        }
-    }
-
-    private void onEntryAddedInternal(final NotificationEntry added) {
-        if (added.isRowRemoved()) {
-            added.setDebugThrowable(new Throwable());
-        }
-        final StatusBarNotification sbn = added.getSbn();
-        boolean isGroupChild = isGroupChild(sbn);
-        String groupKey = getGroupKey(sbn);
-        NotificationGroup group = mGroupMap.get(groupKey);
-        if (group == null) {
-            group = new NotificationGroup(groupKey);
-            mGroupMap.put(groupKey, group);
-            mEventDispatcher.notifyGroupCreated(group);
-        }
-        if (SPEW) {
-            Log.d(TAG, "onEntryAddedInternal: entry=" + logKey(added)
-                    + " group=" + logGroupKey(group));
-        }
-        if (isGroupChild) {
-            NotificationEntry existing = group.children.get(added.getKey());
-            if (existing != null && existing != added) {
-                Throwable existingThrowable = existing.getDebugThrowable();
-                Log.wtf(TAG, "Inconsistent entries found with the same key " + logKey(added)
-                        + "existing removed: " + existing.isRowRemoved()
-                        + (existingThrowable != null
-                                ? Log.getStackTraceString(existingThrowable) + "\n" : "")
-                        + " added removed" + added.isRowRemoved(), new Throwable());
-            }
-            group.children.put(added.getKey(), added);
-            addToPostBatchHistory(group, added);
-            updateSuppression(group);
-        } else {
-            group.summary = added;
-            addToPostBatchHistory(group, added);
-            group.expanded = added.areChildrenExpanded();
-            updateSuppression(group);
-            if (!group.children.isEmpty()) {
-                ArrayList<NotificationEntry> childrenCopy =
-                        new ArrayList<>(group.children.values());
-                for (NotificationEntry child : childrenCopy) {
-                    onEntryBecomingChild(child);
-                }
-                mEventDispatcher.notifyGroupsChanged();
-            }
-        }
-    }
-
-    private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) {
-        if (entry == null) {
-            return;
-        }
-        boolean didAdd = group.postBatchHistory.add(new PostRecord(entry));
-        if (didAdd) {
-            trimPostBatchHistory(group.postBatchHistory);
-        }
-    }
-
-    /** remove all history that's too old to be in the batch. */
-    private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) {
-        if (postBatchHistory.size() <= 1) {
-            return;
-        }
-        long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE;
-        while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) {
-            postBatchHistory.pollFirst();
-        }
-    }
-
-    private void onEntryBecomingChild(NotificationEntry entry) {
-        updateIsolation(entry);
-    }
-
-    private void updateSuppression(NotificationGroup group) {
-        if (group == null) {
-            return;
-        }
-        NotificationEntry prevAlertOverride = group.alertOverride;
-        group.alertOverride = getPriorityConversationAlertOverride(group);
-
-        int childCount = 0;
-        boolean hasBubbles = false;
-        for (NotificationEntry entry : group.children.values()) {
-            if (mBubblesOptional.isPresent() && mBubblesOptional.get()
-                    .isBubbleNotificationSuppressedFromShade(
-                            entry.getKey(), entry.getSbn().getGroupKey())) {
-                hasBubbles = true;
-            } else {
-                childCount++;
-            }
-        }
-
-        boolean prevSuppressed = group.suppressed;
-        group.suppressed = group.summary != null && !group.expanded
-                && (childCount == 1
-                || (childCount == 0
-                && group.summary.getSbn().getNotification().isGroupSummary()
-                && (hasIsolatedChildren(group) || hasBubbles)));
-
-        boolean alertOverrideChanged = prevAlertOverride != group.alertOverride;
-        boolean suppressionChanged = prevSuppressed != group.suppressed;
-        if (alertOverrideChanged || suppressionChanged) {
-            if (DEBUG) {
-                Log.d(TAG, "updateSuppression:"
-                        + " willNotifyListeners=" + !mIsUpdatingUnchangedGroup
-                        + " changes for group:\n" + group);
-                if (alertOverrideChanged) {
-                    Log.d(TAG, "updateSuppression: alertOverride was=" + logKey(prevAlertOverride)
-                            + " now=" + logKey(group.alertOverride));
-                }
-                if (suppressionChanged) {
-                    Log.d(TAG, "updateSuppression: suppressed changed to " + group.suppressed);
-                }
-            }
-            if (alertOverrideChanged) {
-                mEventDispatcher.notifyAlertOverrideChanged(group, prevAlertOverride);
-            }
-            if (suppressionChanged) {
-                mEventDispatcher.notifySuppressedChanged(group);
-            }
-            if (!mIsUpdatingUnchangedGroup) {
-                mEventDispatcher.notifyGroupsChanged();
-            }
-        }
-    }
-
-    /**
-     * Finds the isolated logical child of this group which is should be alerted instead.
-     *
-     * Notifications from priority conversations are isolated from their groups to make them more
-     * prominent, however apps may post these with a GroupAlertBehavior that has the group receiving
-     * the alert.  This would lead to the group alerting even though the conversation that was
-     * updated was not actually a part of that group.  This method finds the best priority
-     * conversation in this situation, if there is one, so they can be set as the alertOverride of
-     * the group.
-     *
-     * @param group the group to check
-     * @return the entry which should receive the alert instead of the group, if any.
-     */
-    @Nullable
-    private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) {
-        // GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior,
-        // but which should be alerting (because priority conversations are isolated), find it.
-        if (group == null || group.summary == null) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary"
-                        + " group=" + logGroupKey(group));
-            }
-            return null;
-        }
-        if (isIsolated(group.summary.getKey())) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: isolated group"
-                        + " group=" + logGroupKey(group));
-            }
-            return null;
-        }
-
-        // Precondiions:
-        // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
-        // * Only necessary when at least one notification in the group is on a priority channel
-        if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
-                == Notification.GROUP_ALERT_CHILDREN) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN"
-                        + " group=" + logGroupKey(group));
-            }
-            return null;
-        }
-
-        // Get the important children first, copy the keys for the final importance check,
-        // then add the non-isolated children to the map for unified lookup.
-        HashMap<String, NotificationEntry> children = getImportantConversations(group);
-        if (children == null || children.isEmpty()) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations"
-                        + " group=" + logGroupKey(group));
-            }
-            return null;
-        }
-        HashSet<String> importantChildKeys = new HashSet<>(children.keySet());
-        children.putAll(group.children);
-
-        // Ensure all children have GROUP_ALERT_SUMMARY
-        for (NotificationEntry child : children.values()) {
-            if (child.getSbn().getNotification().getGroupAlertBehavior()
-                    != Notification.GROUP_ALERT_SUMMARY) {
-                if (SPEW) {
-                    Log.d(TAG, "getPriorityConversationAlertOverride: child != GROUP_ALERT_SUMMARY"
-                            + " group=" + logGroupKey(group));
-                }
-                return null;
-            }
-        }
-
-        // Create a merged post history from all the children
-        TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory);
-        for (String importantChildKey : importantChildKeys) {
-            NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey);
-            combinedHistory.addAll(importantChildGroup.postBatchHistory);
-        }
-        trimPostBatchHistory(combinedHistory);
-
-        // This is a streamlined implementation of the following idea:
-        // * From the subset of notifications in the latest 'batch' of updates.  A batch is:
-        //   * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted.
-        //   * Only including notifs newer than the second-to-last post of any notification.
-        // * Find the newest child in the batch -- the with the largest 'when' value.
-        // * If the newest child is a priority conversation, set that as the override.
-        HashSet<String> batchKeys = new HashSet<>();
-        long newestChildWhen = -1;
-        NotificationEntry newestChild = null;
-        // Iterate backwards through the post history, tracking the child with the smallest sort key
-        for (PostRecord record : combinedHistory.descendingSet()) {
-            if (batchKeys.contains(record.key)) {
-                // Once you see a notification again, the batch has ended
-                break;
-            }
-            batchKeys.add(record.key);
-            NotificationEntry child = children.get(record.key);
-            if (child != null) {
-                long childWhen = child.getSbn().getNotification().when;
-                if (newestChild == null || childWhen > newestChildWhen) {
-                    newestChildWhen = childWhen;
-                    newestChild = child;
-                }
-            }
-        }
-        if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride:"
-                        + " result=" + logKey(newestChild)
-                        + " group=" + logGroupKey(group));
-            }
-            return newestChild;
-        }
-        if (SPEW) {
-            Log.d(TAG, "getPriorityConversationAlertOverride:"
-                    + " result=null newestChild=" + logKey(newestChild)
-                    + " group=" + logGroupKey(group));
-        }
-        return null;
-    }
-
-    private boolean hasIsolatedChildren(NotificationGroup group) {
-        return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0;
-    }
-
-    private int getNumberOfIsolatedChildren(String groupKey) {
-        int count = 0;
-        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
-                count++;
-            }
-        }
-        return count;
-    }
-
-    @Nullable
-    private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) {
-        String groupKey = group.summary.getSbn().getGroupKey();
-        HashMap<String, NotificationEntry> result = null;
-        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey)) {
-                NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary;
-                if (isImportantConversation(entry)) {
-                    if (result == null) {
-                        result = new HashMap<>();
-                    }
-                    result.put(sbn.getKey(), entry);
-                }
-            }
-        }
-        return result;
-    }
-
-    private void setStatusBarState(int newState) {
-        if (newState == StatusBarState.KEYGUARD) {
-            collapseGroups();
-        }
-    }
-
-    private void collapseGroups() {
-        // Because notifications can become isolated when the group becomes suppressed it can
-        // lead to concurrent modifications while looping. We need to make a copy.
-        ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
-        int size = groupCopy.size();
-        for (int i = 0; i < size; i++) {
-            NotificationGroup group =  groupCopy.get(i);
-            if (group.expanded) {
-                setGroupExpanded(group, false);
-            }
-            updateSuppression(group);
-        }
-    }
-
-    public boolean isChildInGroup(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        if (!isGroupChild(sbn)) {
-            return false;
-        }
-        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
-        if (group == null || group.summary == null || group.suppressed) {
-            return false;
-        }
-        if (group.children.isEmpty()) {
-            // If the suppression of a group changes because the last child was removed, this can
-            // still be called temporarily because the child hasn't been fully removed yet. Let's
-            // make sure we still return false in that case.
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * If there is a {@link NotificationGroup} associated with the provided entry, this method
-     * will update the suppression of that group.
-     */
-    public void updateSuppression(NotificationEntry entry) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
-        if (group != null) {
-            updateSuppression(group);
-        }
-    }
-
-    /**
-     * Get the group key. May differ from the one in the notification due to the notification
-     * being temporarily isolated.
-     *
-     * @param sbn notification to check
-     * @return the key of the notification
-     */
-    private String getGroupKey(StatusBarNotification sbn) {
-        return getGroupKey(sbn.getKey(), sbn.getGroupKey());
-    }
-
-    private String getGroupKey(String key, String groupKey) {
-        if (isIsolated(key)) {
-            return key;
-        }
-        return groupKey;
-    }
-
-    private boolean isIsolated(String sbnKey) {
-        return mIsolatedEntries.containsKey(sbnKey);
-    }
-
-    /**
-     * Whether a notification is visually a group child.
-     *
-     * @param sbn notification to check
-     * @return true if it is visually a group child
-     */
-    private boolean isGroupChild(StatusBarNotification sbn) {
-        return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
-    }
-
-    private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
-        if (isIsolated(key)) {
-            return false;
-        }
-        return isGroup && !isGroupSummary;
-    }
-
-    /**
-     * Whether a notification that is normally part of a group should be temporarily isolated from
-     * the group and put in their own group visually.  This generally happens when the notification
-     * is alerting.
-     *
-     * @param entry the notification to check
-     * @return true if the entry should be isolated
-     */
-    private boolean shouldIsolate(NotificationEntry entry) {
-        StatusBarNotification sbn = entry.getSbn();
-        if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
-            return false;
-        }
-        if (isImportantConversation(entry)) {
-            return true;
-        }
-        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
-        return (sbn.getNotification().fullScreenIntent != null
-                    || notificationGroup == null
-                    || !notificationGroup.expanded
-                    || isGroupNotFullyVisible(notificationGroup));
-    }
-
-    private boolean isImportantConversation(NotificationEntry entry) {
-        int peopleNotificationType =
-                mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
-        return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON;
-    }
-
-    /**
-     * Isolate a notification from its group so that it visually shows as its own group.
-     *
-     * @param entry the notification to isolate
-     */
-    private void isolateNotification(NotificationEntry entry) {
-        if (SPEW) {
-            Log.d(TAG, "isolateNotification: entry=" + logKey(entry));
-        }
-        // We will be isolated now, so lets update the groups
-        onEntryRemovedInternal(entry, entry.getSbn());
-
-        mIsolatedEntries.put(entry.getKey(), entry.getSbn());
-
-        onEntryAddedInternal(entry);
-        // We also need to update the suppression of the old group, because this call comes
-        // even before the groupManager knows about the notification at all.
-        // When the notification gets added afterwards it is already isolated and therefore
-        // it doesn't lead to an update.
-        updateSuppression(mGroupMap.get(entry.getSbn().getGroupKey()));
-        mEventDispatcher.notifyGroupsChanged();
-    }
-
-    /**
-     * Update the isolation of an entry, splitting it from the group.
-     */
-    private void updateIsolation(NotificationEntry entry) {
-        // We need to buffer a few events because we do isolation changes in 3 steps:
-        // removeInternal, update mIsolatedEntries, addInternal.  This means that often the
-        // alertOverride will update on the removal, however processing the event in that case can
-        // cause problems because the mIsolatedEntries map is not in its final state, so the event
-        // listener may be unable to correctly determine the true state of the group.  By delaying
-        // the alertOverride change until after the add phase, we can ensure that listeners only
-        // have to handle a consistent state.
-        mEventDispatcher.openBufferScope();
-        boolean isIsolated = isIsolated(entry.getSbn().getKey());
-        if (shouldIsolate(entry)) {
-            if (!isIsolated) {
-                isolateNotification(entry);
-            }
-        } else if (isIsolated) {
-            stopIsolatingNotification(entry);
-        }
-        mEventDispatcher.closeBufferScope();
-    }
-
-    /**
-     * Stop isolating a notification and re-group it with its original logical group.
-     *
-     * @param entry the notification to un-isolate
-     */
-    private void stopIsolatingNotification(NotificationEntry entry) {
-        if (SPEW) {
-            Log.d(TAG, "stopIsolatingNotification: entry=" + logKey(entry));
-        }
-        // not isolated anymore, we need to update the groups
-        onEntryRemovedInternal(entry, entry.getSbn());
-        mIsolatedEntries.remove(entry.getKey());
-        onEntryAddedInternal(entry);
-        mEventDispatcher.notifyGroupsChanged();
-    }
-
-    private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) {
-        return notificationGroup.summary == null
-                || notificationGroup.summary.isGroupNotFullyVisible();
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("GroupManagerLegacy state:");
-        pw.println("  number of groups: " +  mGroupMap.size());
-        for (Map.Entry<String, NotificationGroup>  entry : mGroupMap.entrySet()) {
-            pw.println("\n    key: " + logKey(entry.getKey())); pw.println(entry.getValue());
-        }
-        pw.println("\n    isolated entries: " +  mIsolatedEntries.size());
-        for (Map.Entry<String, StatusBarNotification> entry : mIsolatedEntries.entrySet()) {
-            pw.print("      "); pw.print(logKey(entry.getKey()));
-            pw.print(", "); pw.println(entry.getValue());
-        }
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        setStatusBarState(newState);
-    }
-
-    /** Get the group key, reformatted for logging, for the (optional) group */
-    private static String logGroupKey(NotificationGroup group) {
-        if (group == null) {
-            return "null";
-        }
-        return logKey(group.groupKey);
-    }
-
-    /**
-     * A record of a notification being posted, containing the time of the post and the key of the
-     * notification entry.  These are stored in a TreeSet by the NotificationGroup and used to
-     * calculate a batch of notifications.
-     */
-    public static class PostRecord implements Comparable<PostRecord> {
-        public final long postTime;
-        public final String key;
-
-        /** constructs a record containing the post time and key from the notification entry */
-        public PostRecord(@NonNull NotificationEntry entry) {
-            this.postTime = entry.getSbn().getPostTime();
-            this.key = entry.getKey();
-        }
-
-        @Override
-        public int compareTo(PostRecord o) {
-            int postTimeComparison = Long.compare(this.postTime, o.postTime);
-            return postTimeComparison == 0
-                    ? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key)
-                    : postTimeComparison;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            PostRecord that = (PostRecord) o;
-            return postTime == that.postTime && key.equals(that.key);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(postTime, key);
-        }
-    }
-
-    /**
-     * Represents a notification group in the notification shade.
-     */
-    public static class NotificationGroup {
-        public final String groupKey;
-        public final HashMap<String, NotificationEntry> children = new HashMap<>();
-        public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>();
-        public NotificationEntry summary;
-        public boolean expanded;
-        /**
-         * Is this notification group suppressed, i.e its summary is hidden
-         */
-        public boolean suppressed;
-        /**
-         * The child (which is isolated from this group) to which the alert should be transferred,
-         * due to priority conversations.
-         */
-        public NotificationEntry alertOverride;
-
-        NotificationGroup(String groupKey) {
-            this.groupKey = groupKey;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("    groupKey: ").append(groupKey);
-            sb.append("\n    summary:");
-            appendEntry(sb, summary);
-            sb.append("\n    children size: ").append(children.size());
-            for (NotificationEntry child : children.values()) {
-                appendEntry(sb, child);
-            }
-            sb.append("\n    alertOverride:");
-            appendEntry(sb, alertOverride);
-            sb.append("\n    summary suppressed: ").append(suppressed);
-            return sb.toString();
-        }
-
-        private void appendEntry(StringBuilder sb, NotificationEntry entry) {
-            sb.append("\n      ").append(entry != null ? entry.getSbn() : "null");
-            if (entry != null && entry.getDebugThrowable() != null) {
-                sb.append(Log.getStackTraceString(entry.getDebugThrowable()));
-            }
-        }
-    }
-
-    /**
-     * This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}.
-     * When buffering, instead of notifying the listeners it will set internal state that will allow
-     * it to notify listeners of those events later
-     */
-    static class GroupEventDispatcher {
-        private final Function<String, NotificationGroup> mGroupMapGetter;
-        private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
-        private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>();
-        private final HashMap<String, Boolean> mOldSuppressedByGroup = new HashMap<>();
-        private int mBufferScopeDepth = 0;
-        private boolean mDidGroupsChange = false;
-
-        GroupEventDispatcher(Function<String, NotificationGroup> groupMapGetter) {
-            mGroupMapGetter = requireNonNull(groupMapGetter);
-        }
-
-        void registerGroupChangeListener(OnGroupChangeListener listener) {
-            mGroupChangeListeners.add(listener);
-        }
-
-        private boolean isBuffering() {
-            return mBufferScopeDepth > 0;
-        }
-
-        void notifyAlertOverrideChanged(NotificationGroup group,
-                NotificationEntry oldAlertOverride) {
-            if (isBuffering()) {
-                // The value in this map is the override before the event.  If there is an entry
-                // already in the map, then we are effectively coalescing two events, which means
-                // we need to preserve the original initial value.
-                if (!mOldAlertOverrideByGroup.containsKey(group.groupKey)) {
-                    mOldAlertOverrideByGroup.put(group.groupKey, oldAlertOverride);
-                }
-            } else {
-                for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                    listener.onGroupAlertOverrideChanged(group, oldAlertOverride,
-                            group.alertOverride);
-                }
-            }
-        }
-
-        void notifySuppressedChanged(NotificationGroup group) {
-            if (isBuffering()) {
-                // The value in this map is the 'suppressed' before the event.  If there is a value
-                // already in the map, then we are effectively coalescing two events, which means
-                // we need to preserve the original initial value.
-                mOldSuppressedByGroup.putIfAbsent(group.groupKey, !group.suppressed);
-            } else {
-                for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                    listener.onGroupSuppressionChanged(group, group.suppressed);
-                }
-            }
-        }
-
-        void notifyGroupsChanged() {
-            if (isBuffering()) {
-                mDidGroupsChange = true;
-            } else {
-                for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                    listener.onGroupsChanged();
-                }
-            }
-        }
-
-        void notifyGroupCreated(NotificationGroup group) {
-            // NOTE: given how this event is used, it doesn't need to be buffered right now
-            final String groupKey = group.groupKey;
-            for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                listener.onGroupCreated(group, groupKey);
-            }
-        }
-
-        void notifyGroupRemoved(NotificationGroup group) {
-            // NOTE: given how this event is used, it doesn't need to be buffered right now
-            final String groupKey = group.groupKey;
-            for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                listener.onGroupRemoved(group, groupKey);
-            }
-        }
-
-        void openBufferScope() {
-            mBufferScopeDepth++;
-            if (SPEW) {
-                Log.d(TAG, "openBufferScope: scopeDepth=" + mBufferScopeDepth);
-            }
-        }
-
-        void closeBufferScope() {
-            mBufferScopeDepth--;
-            if (SPEW) {
-                Log.d(TAG, "closeBufferScope: scopeDepth=" + mBufferScopeDepth);
-            }
-            // Flush buffered events if the last buffer scope has closed
-            if (!isBuffering()) {
-                flushBuffer();
-            }
-        }
-
-        private void flushBuffer() {
-            if (SPEW) {
-                Log.d(TAG, "flushBuffer: "
-                        + " suppressed.size=" + mOldSuppressedByGroup.size()
-                        + " alertOverride.size=" + mOldAlertOverrideByGroup.size()
-                        + " mDidGroupsChange=" + mDidGroupsChange);
-            }
-            // alert all group suppressed changes for groups that were not removed
-            for (Map.Entry<String, Boolean> entry : mOldSuppressedByGroup.entrySet()) {
-                NotificationGroup group = mGroupMapGetter.apply(entry.getKey());
-                if (group == null) {
-                    // The group can be null if this suppressed changed before the group was
-                    // permanently removed, meaning that there's no guarantee that listeners will
-                    // that field clear.
-                    if (SPEW) {
-                        Log.d(TAG, "flushBuffer: suppressed:"
-                                + " cannot report for removed group: " + logKey(entry.getKey()));
-                    }
-                    continue;
-                }
-                boolean oldSuppressed = entry.getValue();
-                if (group.suppressed == oldSuppressed) {
-                    // If the final suppressed equals the initial, it means we coalesced two
-                    // events which undid the change, so we can drop it entirely.
-                    if (SPEW) {
-                        Log.d(TAG, "flushBuffer: suppressed:"
-                                + " did not change for group: " + logKey(entry.getKey()));
-                    }
-                    continue;
-                }
-                notifySuppressedChanged(group);
-            }
-            mOldSuppressedByGroup.clear();
-            // alert all group alert override changes for groups that were not removed
-            for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) {
-                NotificationGroup group = mGroupMapGetter.apply(entry.getKey());
-                if (group == null) {
-                    // The group can be null if this alertOverride changed before the group was
-                    // permanently removed, meaning that there's no guarantee that listeners will
-                    // that field clear.
-                    if (SPEW) {
-                        Log.d(TAG, "flushBuffer: alertOverride:"
-                                + " cannot report for removed group: " + entry.getKey());
-                    }
-                    continue;
-                }
-                NotificationEntry oldAlertOverride = entry.getValue();
-                if (group.alertOverride == oldAlertOverride) {
-                    // If the final alertOverride equals the initial, it means we coalesced two
-                    // events which undid the change, so we can drop it entirely.
-                    if (SPEW) {
-                        Log.d(TAG, "flushBuffer: alertOverride:"
-                                + " did not change for group: " + logKey(entry.getKey()));
-                    }
-                    continue;
-                }
-                notifyAlertOverrideChanged(group, oldAlertOverride);
-            }
-            mOldAlertOverrideByGroup.clear();
-            // alert that groups changed
-            if (mDidGroupsChange) {
-                notifyGroupsChanged();
-                mDidGroupsChange = false;
-            }
-        }
-    }
-
-    /**
-     * Listener for group changes not including group expansion changes which are handled by
-     * {@link OnGroupExpansionChangeListener}.
-     */
-    public interface OnGroupChangeListener {
-        /**
-         * A new group has been created.
-         *
-         * @param group the group that was created
-         * @param groupKey the group's key
-         */
-        default void onGroupCreated(
-                NotificationGroup group,
-                String groupKey) {}
-
-        /**
-         * A group has been removed.
-         *
-         * @param group the group that was removed
-         * @param groupKey the group's key
-         */
-        default void onGroupRemoved(
-                NotificationGroup group,
-                String groupKey) {}
-
-        /**
-         * The suppression of a group has changed.
-         *
-         * @param group the group that has changed
-         * @param suppressed true if the group is now suppressed, false o/w
-         */
-        default void onGroupSuppressionChanged(
-                NotificationGroup group,
-                boolean suppressed) {}
-
-        /**
-         * The alert override of a group has changed.
-         *
-         * @param group the group that has changed
-         * @param oldAlertOverride the previous notification to which the group's alerts were sent
-         * @param newAlertOverride the notification to which the group's alerts should now be sent
-         */
-        default void onGroupAlertOverrideChanged(
-                NotificationGroup group,
-                @Nullable NotificationEntry oldAlertOverride,
-                @Nullable NotificationEntry newAlertOverride) {}
-
-        /**
-         * The groups have changed. This can happen if the isolation of a child has changes or if a
-         * group became suppressed / unsuppressed
-         */
-        default void onGroupsChanged() {}
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
deleted file mode 100644
index bb8c0e0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.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.systemui.statusbar.notification.collection.legacy;
-
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-
-/**
- * A manager that ensures that notifications are visually stable. It will suppress reorderings
- * and reorder at the right time when they are out of view.
- */
-public class VisualStabilityManager {
-
-    private final VisualStabilityProvider mVisualStabilityProvider;
-
-    private boolean mPanelExpanded;
-    private boolean mScreenOn;
-    private boolean mPulsing;
-
-    /**
-     * Injected constructor. See {@link NotificationsModule}.
-     */
-    public VisualStabilityManager(
-            VisualStabilityProvider visualStabilityProvider,
-            StatusBarStateController statusBarStateController,
-            WakefulnessLifecycle wakefulnessLifecycle) {
-
-        mVisualStabilityProvider = visualStabilityProvider;
-
-        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);
-        }
-    }
-
-    /**
-     * @param screenOn whether the screen is on
-     */
-    private void setScreenOn(boolean screenOn) {
-        mScreenOn = screenOn;
-        updateAllowedStates();
-    }
-
-    /**
-     * Set the panel to be expanded.
-     */
-    private void setPanelExpanded(boolean expanded) {
-        mPanelExpanded = expanded;
-        updateAllowedStates();
-    }
-
-    /**
-     * @param pulsing whether we are currently pulsing for ambient display.
-     */
-    private void setPulsing(boolean pulsing) {
-        if (mPulsing == pulsing) {
-            return;
-        }
-        mPulsing = pulsing;
-        updateAllowedStates();
-    }
-
-    private void updateAllowedStates() {
-        boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
-        mVisualStabilityProvider.setReorderingAllowed(reorderingAllowed);
-    }
-
-    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/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt
new file mode 100644
index 0000000..8bce5b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import android.util.ArrayMap
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+
+class NotifCollectionInconsistencyTracker(val logger: NotifCollectionLogger) {
+    fun attach(
+        collectedKeySetAccessor: () -> Set<String>,
+        coalescedKeySetAccessor: () -> Set<String>,
+    ) {
+        if (attached) {
+            throw RuntimeException("attach() called twice")
+        }
+        attached = true
+        this.collectedKeySetAccessor = collectedKeySetAccessor
+        this.coalescedKeySetAccessor = coalescedKeySetAccessor
+    }
+
+    fun logNewMissingNotifications(rankingMap: RankingMap) {
+        val currentCollectedKeys = collectedKeySetAccessor()
+        val currentCoalescedKeys = coalescedKeySetAccessor()
+        val newMissingNotifications = rankingMap.orderedKeys.asSequence()
+            .filter { it !in currentCollectedKeys }
+            .filter { it !in currentCoalescedKeys }
+            .toSet()
+        maybeLogMissingNotifications(missingNotifications, newMissingNotifications)
+        missingNotifications = newMissingNotifications
+    }
+
+    @VisibleForTesting
+    fun maybeLogMissingNotifications(
+        oldMissingKeys: Set<String>,
+        newMissingKeys: Set<String>,
+    ) {
+        if (oldMissingKeys.isEmpty() && newMissingKeys.isEmpty()) return
+        if (oldMissingKeys == newMissingKeys) return
+        (oldMissingKeys - newMissingKeys).sorted().let { justFound ->
+            if (justFound.isNotEmpty()) {
+                logger.logFoundNotifications(justFound, newMissingKeys.size)
+            }
+        }
+        (newMissingKeys - oldMissingKeys).sorted().let { goneMissing ->
+            if (goneMissing.isNotEmpty()) {
+                logger.logMissingNotifications(goneMissing, newMissingKeys.size)
+            }
+        }
+    }
+
+    fun logNewInconsistentRankings(
+        currentEntriesWithoutRankings: ArrayMap<String, NotificationEntry>?,
+        rankingMap: RankingMap,
+    ) {
+        maybeLogInconsistentRankings(
+            notificationsWithoutRankings,
+            currentEntriesWithoutRankings ?: emptyMap(),
+            rankingMap
+        )
+        notificationsWithoutRankings = currentEntriesWithoutRankings?.keys ?: emptySet()
+    }
+
+    @VisibleForTesting
+    fun maybeLogInconsistentRankings(
+        oldKeysWithoutRankings: Set<String>,
+        newEntriesWithoutRankings: Map<String, NotificationEntry>,
+        rankingMap: RankingMap,
+    ) {
+        if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings.isEmpty()) return
+        if (oldKeysWithoutRankings == newEntriesWithoutRankings.keys) return
+        val newlyConsistent: List<String> = oldKeysWithoutRankings
+            .mapNotNull { key ->
+                key.takeIf { key !in newEntriesWithoutRankings }
+                    .takeIf { key in rankingMap.orderedKeys }
+            }.sorted()
+        if (newlyConsistent.isNotEmpty()) {
+            val totalInconsistent: Int = newEntriesWithoutRankings.size
+            logger.logRecoveredRankings(newlyConsistent, totalInconsistent)
+        }
+        val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
+            .mapNotNull { (key, entry) ->
+                entry.takeIf { key !in oldKeysWithoutRankings }
+            }.sortedBy { it.key }
+        if (newlyInconsistent.isNotEmpty()) {
+            val totalInconsistent: Int = newEntriesWithoutRankings.size
+            logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
+        }
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("notificationsWithoutRankings: ${notificationsWithoutRankings.size}")
+        for (key in notificationsWithoutRankings) {
+            pw.println("\t * : $key")
+        }
+        pw.println("missingNotifications: ${missingNotifications.size}")
+        for (key in missingNotifications) {
+            pw.println("\t * : $key")
+        }
+    }
+
+    private var attached: Boolean = false
+    private lateinit var collectedKeySetAccessor: (() -> Set<String>)
+    private lateinit var coalescedKeySetAccessor: (() -> Set<String>)
+    private var notificationsWithoutRankings = emptySet<String>()
+    private var missingNotifications = emptySet<String>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index ebcac6b..aa27e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -215,8 +215,9 @@
         })
     }
 
-    fun logRecoveredRankings(newlyConsistentKeys: List<String>) {
+    fun logRecoveredRankings(newlyConsistentKeys: List<String>, totalInconsistent: Int) {
         buffer.log(TAG, INFO, {
+            int1 = totalInconsistent
             int1 = newlyConsistentKeys.size
             str1 = newlyConsistentKeys.joinToString { logKey(it) ?: "null" }
         }, {
@@ -224,6 +225,32 @@
         })
     }
 
+    fun logMissingNotifications(
+        newlyMissingKeys: List<String>,
+        totalMissing: Int,
+    ) {
+        buffer.log(TAG, WARNING, {
+            int1 = totalMissing
+            int2 = newlyMissingKeys.size
+            str1 = newlyMissingKeys.joinToString { logKey(it) ?: "null" }
+        }, {
+            "Collection missing $int1 entries in ranking update. Just lost $int2: $str1"
+        })
+    }
+
+    fun logFoundNotifications(
+        newlyFoundKeys: List<String>,
+        totalMissing: Int,
+    ) {
+        buffer.log(TAG, INFO, {
+            int1 = totalMissing
+            int2 = newlyFoundKeys.size
+            str1 = newlyFoundKeys.joinToString { logKey(it) ?: "null" }
+        }, {
+            "Collection missing $int1 entries in ranking update. Just found $int2: $str1"
+        })
+    }
+
     fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) {
         buffer.log(TAG, WTF, {
             str1 = entry.logKey
@@ -362,29 +389,4 @@
     }
 }
 
-fun maybeLogInconsistentRankings(
-    logger: NotifCollectionLogger,
-    oldKeysWithoutRankings: Set<String>,
-    newEntriesWithoutRankings: Map<String, NotificationEntry>?,
-    rankingMap: RankingMap
-) {
-    if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings == null) return
-    val newlyConsistent: List<String> = oldKeysWithoutRankings
-        .mapNotNull { key ->
-            key.takeIf { key !in (newEntriesWithoutRankings ?: emptyMap()) }
-                .takeIf { key in rankingMap.orderedKeys }
-        }.sorted()
-    if (newlyConsistent.isNotEmpty()) {
-        logger.logRecoveredRankings(newlyConsistent)
-    }
-    val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
-        ?.mapNotNull { (key, entry) ->
-            entry.takeIf { key !in oldKeysWithoutRankings }
-        }?.sortedBy { it.key } ?: emptyList()
-    if (newlyInconsistent.isNotEmpty()) {
-        val totalInconsistent: Int = newEntriesWithoutRankings?.size ?: 0
-        logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
-    }
-}
-
 private const val TAG = "NotifCollection"
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 eda2eec..9333c2a 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
@@ -24,22 +24,18 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
 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.dagger.qualifiers.UiBackground;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.NotifPanelEventsModule;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -53,12 +49,9 @@
 import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-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.collection.provider.NotificationVisibilityProviderImpl;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -84,7 +77,6 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.wmshell.BubblesManager;
 
 import java.util.Optional;
@@ -118,16 +110,8 @@
     @SysUISingleton
     @Provides
     static NotificationEntryManager provideNotificationEntryManager(
-            NotificationEntryManagerLogger logger,
-            NotificationGroupManagerLegacy groupManager,
-            NotifPipelineFlags notifPipelineFlags,
-            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
-            LeakDetector leakDetector,
-            IStatusBarService statusBarService,
-            @Background Executor bgExecutor) {
-        return new NotificationEntryManager(
-                logger
-        );
+            NotificationEntryManagerLogger logger) {
+        return new NotificationEntryManager(logger);
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
@@ -141,7 +125,6 @@
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
-            NotificationEntryManager notificationEntryManager,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
@@ -177,20 +160,6 @@
     @Binds
     NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
 
-    /** Provides an instance of {@link VisualStabilityManager} */
-    @SysUISingleton
-    @Provides
-    static VisualStabilityManager provideVisualStabilityManager(
-            VisualStabilityProvider visualStabilityProvider,
-            StatusBarStateController statusBarStateController,
-            WakefulnessLifecycle wakefulnessLifecycle) {
-        return new VisualStabilityManager(
-                visualStabilityProvider,
-                statusBarStateController,
-                wakefulnessLifecycle
-        );
-    }
-
     /** Provides an instance of {@link NotificationLogger} */
     @SysUISingleton
     @Provides
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 385fbb5..be9f133 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
@@ -27,20 +27,20 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.NotificationClicker
 import com.android.systemui.statusbar.notification.NotificationEntryManager
-import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 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.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.io.PrintWriter
@@ -65,7 +65,6 @@
     private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val notificationLogger: NotificationLogger,
-    private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val notificationsMediaManager: NotificationMediaManager,
     private val headsUpViewBinder: HeadsUpViewBinder,
@@ -85,12 +84,11 @@
     ) {
         notificationListener.registerAsSystemService()
 
-        val listController =
-                NotificationListController(
-                        entryManager,
-                        listContainer,
-                        deviceProvisionedController)
-        listController.bind()
+        notifPipeline.get().addCollectionListener(object : NotifCollectionListener {
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                listContainer.cleanUpViewStateForEntry(entry)
+            }
+        })
 
         notificationRowBinder.setNotificationClicker(
                 clickerBuilder.build(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt
new file mode 100644
index 0000000..4cf3572
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.people
+
+import android.service.notification.StatusBarNotification
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.NotificationPersonExtractorPlugin
+import com.android.systemui.statusbar.policy.ExtensionController
+import javax.inject.Inject
+
+interface NotificationPersonExtractor {
+    fun isPersonNotification(sbn: StatusBarNotification): Boolean
+}
+
+@SysUISingleton
+class NotificationPersonExtractorPluginBoundary @Inject constructor(
+    extensionController: ExtensionController
+) : NotificationPersonExtractor {
+
+    private var plugin: NotificationPersonExtractorPlugin? = null
+
+    init {
+        plugin = extensionController
+                .newExtension(NotificationPersonExtractorPlugin::class.java)
+                .withPlugin(NotificationPersonExtractorPlugin::class.java)
+                .withCallback { extractor ->
+                    plugin = extractor
+                }
+                .build()
+                .get()
+    }
+
+    override fun isPersonNotification(sbn: StatusBarNotification): Boolean =
+            plugin?.isPersonNotification(sbn) ?: false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
deleted file mode 100644
index 3af6ba8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ /dev/null
@@ -1,55 +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.statusbar.notification.people
-
-import android.graphics.drawable.Drawable
-
-/**
- * `ViewModel` for PeopleHub view.
- *
- * @param people ViewModels for individual people in PeopleHub, in order that they should be
- *  displayed
- * @param isVisible Whether or not the whole PeopleHub UI is visible
- **/
-data class PeopleHubViewModel(val people: Sequence<PersonViewModel>, val isVisible: Boolean)
-
-/** `ViewModel` for a single "Person' in PeopleHub. */
-data class PersonViewModel(
-    val name: CharSequence,
-    val icon: Drawable,
-    val onClick: () -> Unit
-)
-
-/**
- * `Model` for PeopleHub.
- *
- * @param people Models for individual people in PeopleHub, in order that they should be displayed
- **/
-data class PeopleHubModel(val people: Collection<PersonModel>)
-
-/** `Model` for a single "Person" in PeopleHub. */
-data class PersonModel(
-    val key: PersonKey,
-    val userId: Int,
-    // TODO: these should live in the ViewModel
-    val name: CharSequence,
-    val avatar: Drawable,
-    val clickRunnable: Runnable
-)
-
-/** Unique identifier for a Person in PeopleHub. */
-typealias PersonKey = String
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 16574ab..c17ffb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -21,25 +21,6 @@
 
 @Module
 abstract class PeopleHubModule {
-
-    @Binds
-    abstract fun peopleHubSectionFooterViewAdapter(
-        impl: PeopleHubViewAdapterImpl
-    ): PeopleHubViewAdapter
-
-    @Binds
-    abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
-
-    @Binds
-    abstract fun peopleHubSettingChangeDataSource(
-        impl: PeopleHubSettingChangeDataSourceImpl
-    ): DataSource<Boolean>
-
-    @Binds
-    abstract fun peopleHubViewModelFactoryDataSource(
-        impl: PeopleHubViewModelFactoryDataSourceImpl
-    ): DataSource<PeopleHubViewModelFactory>
-
     @Binds
     abstract fun peopleNotificationIdentifier(
         impl: PeopleNotificationIdentifierImpl
@@ -49,4 +30,4 @@
     abstract fun notificationPersonExtractor(
         pluginImpl: NotificationPersonExtractorPluginBoundary
     ): NotificationPersonExtractor
-}
\ No newline at end of file
+}
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
deleted file mode 100644
index 6062941..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ /dev/null
@@ -1,315 +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.statusbar.notification.people
-
-import android.app.Notification
-import android.content.Context
-import android.content.pm.LauncherApps
-import android.content.pm.PackageManager
-import android.content.pm.UserInfo
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import android.service.notification.NotificationListenerService
-import android.service.notification.NotificationListenerService.REASON_SNOOZED
-import android.service.notification.StatusBarNotification
-import android.util.IconDrawableFactory
-import android.util.SparseArray
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-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
-import com.android.systemui.statusbar.NotificationListener
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.policy.ExtensionController
-import java.util.ArrayDeque
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-private const val MAX_STORED_INACTIVE_PEOPLE = 10
-
-interface NotificationPersonExtractor {
-    fun extractPerson(sbn: StatusBarNotification): PersonModel?
-    fun extractPersonKey(sbn: StatusBarNotification): String?
-    fun isPersonNotification(sbn: StatusBarNotification): Boolean
-}
-
-@SysUISingleton
-class NotificationPersonExtractorPluginBoundary @Inject constructor(
-    extensionController: ExtensionController
-) : NotificationPersonExtractor {
-
-    private var plugin: NotificationPersonExtractorPlugin? = null
-
-    init {
-        plugin = extensionController
-                .newExtension(NotificationPersonExtractorPlugin::class.java)
-                .withPlugin(NotificationPersonExtractorPlugin::class.java)
-                .withCallback { extractor ->
-                    plugin = extractor
-                }
-                .build()
-                .get()
-    }
-
-    override fun extractPerson(sbn: StatusBarNotification) =
-            plugin?.extractPerson(sbn)?.run {
-                PersonModel(key, sbn.user.identifier, name, avatar, clickRunnable)
-            }
-
-    override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
-
-    override fun isPersonNotification(sbn: StatusBarNotification): Boolean =
-            plugin?.isPersonNotification(sbn) ?: false
-}
-
-@SysUISingleton
-class PeopleHubDataSourceImpl @Inject constructor(
-    private val notifCollection: CommonNotifCollection,
-    private val extractor: NotificationPersonExtractor,
-    private val userManager: UserManager,
-    launcherApps: LauncherApps,
-    packageManager: PackageManager,
-    context: Context,
-    private val notificationListener: NotificationListener,
-    @Background private val bgExecutor: Executor,
-    @Main private val mainExecutor: Executor,
-    private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
-) : DataSource<PeopleHubModel> {
-
-    private var userChangeSubscription: Subscription? = null
-    private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
-    private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
-
-    private val iconFactory = run {
-        val appContext = context.applicationContext
-        ConversationIconFactory(
-                appContext,
-                launcherApps,
-                packageManager,
-                IconDrawableFactory.newInstance(appContext),
-                appContext.resources.getDimensionPixelSize(
-                        R.dimen.notification_guts_conversation_icon_size
-                )
-        )
-    }
-
-    private val notifCollectionListener = object : NotifCollectionListener {
-        override fun onEntryAdded(entry: NotificationEntry) = addVisibleEntry(entry)
-        override fun onEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
-        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) =
-            removeVisibleEntry(entry, reason)
-    }
-
-    private fun removeVisibleEntry(entry: NotificationEntry, reason: Int) {
-        (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
-            val userId = entry.sbn.user.identifier
-            bgExecutor.execute {
-                val parentId = userManager.getProfileParent(userId)?.id ?: userId
-                mainExecutor.execute {
-                    if (reason == REASON_SNOOZED) {
-                        if (peopleHubManagerForUser[parentId]?.migrateActivePerson(key) == true) {
-                            updateUi()
-                        }
-                    } else {
-                        peopleHubManagerForUser[parentId]?.removeActivePerson(key)
-                    }
-                }
-            }
-        }
-    }
-
-    private fun addVisibleEntry(entry: NotificationEntry) {
-        entry.extractPerson()?.let { personModel ->
-            val userId = entry.sbn.user.identifier
-            bgExecutor.execute {
-                val parentId = userManager.getProfileParent(userId)?.id ?: userId
-                mainExecutor.execute {
-                    val manager = peopleHubManagerForUser[parentId]
-                            ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) }
-                    if (manager.addActivePerson(personModel)) {
-                        updateUi()
-                    }
-                }
-            }
-        }
-    }
-
-    override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
-        val register = dataListeners.isEmpty()
-        dataListeners.add(listener)
-        if (register) {
-            userChangeSubscription = notifLockscreenUserMgr.registerListener(
-                    object : NotificationLockscreenUserManager.UserChangedListener {
-                        override fun onUserChanged(userId: Int) = updateUi()
-                        override fun onCurrentProfilesChanged(
-                            currentProfiles: SparseArray<UserInfo>?
-                        ) = updateUi()
-                    })
-            notifCollection.addCollectionListener(notifCollectionListener)
-        } else {
-            getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged)
-        }
-        return object : Subscription {
-            override fun unsubscribe() {
-                dataListeners.remove(listener)
-                if (dataListeners.isEmpty()) {
-                    userChangeSubscription?.unsubscribe()
-                    userChangeSubscription = null
-                    notifCollection.removeCollectionListener(notifCollectionListener)
-                }
-            }
-        }
-    }
-
-    private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? {
-        val currentUserId = notifLockscreenUserMgr.currentUserId
-        val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel()
-                ?: return null
-        val currentProfiles = notifLockscreenUserMgr.currentProfiles
-        return model.copy(people = model.people.filter { person ->
-            currentProfiles[person.userId]?.isQuietModeEnabled == false
-        })
-    }
-
-    private fun updateUi() {
-        val model = getPeopleHubModelForCurrentUser() ?: return
-        for (listener in dataListeners) {
-            listener.onDataChanged(model)
-        }
-    }
-
-    private fun NotificationEntry.extractPerson(): PersonModel? {
-        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
-        if (type == TYPE_NON_PERSON) {
-            return null
-        }
-        val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
-        val extras = sbn.notification.extras
-        val name = ranking.conversationShortcutInfo?.label
-                ?: extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE)
-                ?: extras.getCharSequence(Notification.EXTRA_TITLE)
-                ?: return null
-        val drawable = ranking.getIcon(iconFactory, sbn)
-                ?: iconFactory.getConversationDrawable(
-                        extractAvatarFromRow(this),
-                        sbn.packageName,
-                        sbn.uid,
-                        ranking.channel.isImportantConversation
-                )
-        return PersonModel(key, sbn.user.identifier, name, drawable, clickRunnable)
-    }
-
-    private fun NotificationListenerService.Ranking.getIcon(
-        iconFactory: ConversationIconFactory,
-        sbn: StatusBarNotification
-    ): Drawable? =
-            conversationShortcutInfo?.let { conversationShortcutInfo ->
-                iconFactory.getConversationDrawable(
-                        conversationShortcutInfo,
-                        sbn.packageName,
-                        sbn.uid,
-                        channel.isImportantConversation
-                )
-            }
-
-    private fun NotificationEntry.extractPersonKey(): PersonKey? {
-        // TODO migrate to shortcut id when snoozing is conversation wide
-        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
-        return if (type != TYPE_NON_PERSON) key else null
-    }
-}
-
-private fun NotificationLockscreenUserManager.registerListener(
-    listener: NotificationLockscreenUserManager.UserChangedListener
-): Subscription {
-    addUserChangedListener(listener)
-    return object : Subscription {
-        override fun unsubscribe() {
-            removeUserChangedListener(listener)
-        }
-    }
-}
-
-class PeopleHubManager {
-
-    // People currently visible in the notification shade, and so are not in the hub
-    private val activePeople = mutableMapOf<PersonKey, PersonModel>()
-
-    // People that were once "active" and have been dismissed, and so can be displayed in the hub
-    private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)
-
-    fun migrateActivePerson(key: PersonKey): Boolean {
-        activePeople.remove(key)?.let { data ->
-            if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
-                inactivePeople.removeLast()
-            }
-            inactivePeople.addFirst(data)
-            return true
-        }
-        return false
-    }
-
-    fun removeActivePerson(key: PersonKey) {
-        activePeople.remove(key)
-    }
-
-    fun addActivePerson(person: PersonModel): Boolean {
-        activePeople[person.key] = person
-        return inactivePeople.removeIf { it.key == person.key }
-    }
-
-    fun getPeopleHubModel(): PeopleHubModel = PeopleHubModel(inactivePeople)
-}
-
-private val ViewGroup.children
-    get(): Sequence<View> = sequence {
-        for (i in 0 until childCount) {
-            yield(getChildAt(i))
-        }
-    }
-
-private fun ViewGroup.childrenWithId(id: Int): Sequence<View> = children.filter { it.id == id }
-
-fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
-        entry.row
-                ?.childrenWithId(R.id.expanded)
-                ?.mapNotNull { it as? ViewGroup }
-                ?.flatMap {
-                    it.childrenWithId(com.android.internal.R.id.status_bar_latest_event_content)
-                }
-                ?.mapNotNull {
-                    it.findViewById<ViewGroup>(com.android.internal.R.id.notification_messaging)
-                }
-                ?.mapNotNull { messagesView ->
-                    messagesView.children
-                            .mapNotNull { it as? MessagingGroup }
-                            .lastOrNull()
-                            ?.findViewById<ImageView>(com.android.internal.R.id.message_icon)
-                            ?.drawable
-                }
-                ?.firstOrNull()
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
deleted file mode 100644
index 55bd77f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ /dev/null
@@ -1,193 +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.statusbar.notification.people
-
-import android.content.Context
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-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
-
-/** Boundary between the View and PeopleHub, as seen by the View. */
-interface PeopleHubViewAdapter {
-    fun bindView(viewBoundary: PeopleHubViewBoundary): Subscription
-}
-
-/** Abstract `View` representation of PeopleHub. */
-interface PeopleHubViewBoundary {
-    /** View used for animating the activity launch caused by clicking a person in the hub. */
-    val associatedViewForClickAnimation: View
-
-    /**
-     * [DataListener]s for individual people in the hub.
-     *
-     * These listeners should be ordered such that the first element will be bound to the most
-     * recent person to be added to the hub, and then continuing in descending order. If there are
-     * not enough people to satisfy each listener, `null` will be passed instead, indicating that
-     * the `View` should render a placeholder.
-     */
-    val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
-
-    /** Sets the visibility of the Hub in the notification shade. */
-    fun setVisible(isVisible: Boolean)
-}
-
-/** Creates a [PeopleHubViewModel] given some additional information required from the `View`. */
-interface PeopleHubViewModelFactory {
-
-    /**
-     * Creates a [PeopleHubViewModel] that, when clicked, starts an activity using an animation
-     * involving the given [view].
-     */
-    fun createWithAssociatedClickView(view: View): PeopleHubViewModel
-}
-
-/**
- * Wraps a [PeopleHubViewBoundary] in a [DataListener], and connects it to the data
- * pipeline.
- *
- * @param dataSource PeopleHub data pipeline.
- */
-@SysUISingleton
-class PeopleHubViewAdapterImpl @Inject constructor(
-    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
-) : PeopleHubViewAdapter {
-
-    override fun bindView(viewBoundary: PeopleHubViewBoundary): Subscription =
-            dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
-}
-
-private class PeopleHubDataListenerImpl(
-    private val viewBoundary: PeopleHubViewBoundary
-) : DataListener<PeopleHubViewModelFactory> {
-
-    override fun onDataChanged(data: PeopleHubViewModelFactory) {
-        val viewModel = data.createWithAssociatedClickView(
-                viewBoundary.associatedViewForClickAnimation
-        )
-        viewBoundary.setVisible(viewModel.isVisible)
-        val padded = viewModel.people + repeated(null)
-        for ((adapter, model) in viewBoundary.personViewAdapters.zip(padded)) {
-            adapter.onDataChanged(model)
-        }
-    }
-}
-
-/**
- * Converts [PeopleHubModel]s into [PeopleHubViewModelFactory]s.
- *
- * This class serves as the glue between the View layer (which depends on
- * [PeopleHubViewBoundary]) and the Data layer (which produces [PeopleHubModel]s).
- */
-@SysUISingleton
-class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
-    private val activityStarter: ActivityStarter,
-    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
-) : DataSource<PeopleHubViewModelFactory> {
-
-    override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>): Subscription {
-        var model: PeopleHubModel? = null
-
-        fun updateListener() {
-            // don't invoke listener until we've received our first model
-            model?.let { model ->
-                val factory = PeopleHubViewModelFactoryImpl(model, activityStarter)
-                listener.onDataChanged(factory)
-            }
-        }
-        val dataSub = dataSource.registerListener(object : DataListener<PeopleHubModel> {
-            override fun onDataChanged(data: PeopleHubModel) {
-                model = data
-                updateListener()
-            }
-        })
-        return object : Subscription {
-            override fun unsubscribe() {
-                dataSub.unsubscribe()
-            }
-        }
-    }
-}
-
-private object EmptyViewModelFactory : PeopleHubViewModelFactory {
-    override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
-        return PeopleHubViewModel(emptySequence(), false)
-    }
-}
-
-private class PeopleHubViewModelFactoryImpl(
-    private val model: PeopleHubModel,
-    private val activityStarter: ActivityStarter
-) : PeopleHubViewModelFactory {
-
-    override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
-        val personViewModels = model.people.asSequence().map { personModel ->
-            val onClick = {
-                personModel.clickRunnable.run()
-            }
-            PersonViewModel(personModel.name, personModel.avatar, onClick)
-        }
-        return PeopleHubViewModel(personViewModels, model.people.isNotEmpty())
-    }
-}
-
-@SysUISingleton
-class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
-    @Main private val handler: Handler,
-    context: Context
-) : DataSource<Boolean> {
-
-    private val settingUri = Settings.Secure.getUriFor(Settings.Secure.PEOPLE_STRIP)
-    private val contentResolver = context.contentResolver
-
-    override fun registerListener(listener: DataListener<Boolean>): Subscription {
-        // Immediately report current value of setting
-        updateListener(listener)
-        val observer = object : ContentObserver(handler) {
-            override fun onChange(selfChange: Boolean, uri: Uri?, flags: Int) {
-                super.onChange(selfChange, uri, flags)
-                updateListener(listener)
-            }
-        }
-        contentResolver.registerContentObserver(settingUri, false, observer, UserHandle.USER_ALL)
-        return object : Subscription {
-            override fun unsubscribe() = contentResolver.unregisterContentObserver(observer)
-        }
-    }
-
-    private fun updateListener(listener: DataListener<Boolean>) {
-        val setting = Settings.Secure.getIntForUser(
-                contentResolver,
-                Settings.Secure.PEOPLE_STRIP,
-                0,
-                UserHandle.USER_CURRENT
-        )
-        listener.onDataChanged(setting != 0)
-    }
-}
-
-private fun <T> repeated(value: T): Sequence<T> = sequence {
-    while (true) {
-        yield(value)
-    }
-}
\ No newline at end of file
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 855390d..8574f87 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
@@ -25,8 +25,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -68,6 +66,9 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -3247,6 +3248,7 @@
     }
 
     @Override
+    @NonNull
     public ExpandableViewState createExpandableViewState() {
         return new NotificationViewState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 8f73b80..1e09b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -29,6 +29,7 @@
 import android.view.ViewParent;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.systemui.Dumpable;
@@ -66,7 +67,7 @@
     protected float mContentTransformationAmount;
     protected boolean mIsLastChild;
     protected int mContentShift;
-    private final ExpandableViewState mViewState;
+    @NonNull private final ExpandableViewState mViewState;
     private float mContentTranslation;
     protected boolean mLastInSection;
     protected boolean mFirstInSection;
@@ -610,6 +611,7 @@
 
     public void setActualHeightAnimating(boolean animating) {}
 
+    @NonNull
     protected ExpandableViewState createExpandableViewState() {
         return new ExpandableViewState();
     }
@@ -642,7 +644,12 @@
         return mViewState;
     }
 
-    @Nullable public ExpandableViewState getViewState() {
+    /**
+     * Get the {@link ExpandableViewState} associated with the view.
+     *
+     * @return the ExpandableView's view state.
+     */
+    @NonNull public ExpandableViewState getViewState() {
         return mViewState;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index e43ecf7..49dc655 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -23,6 +23,8 @@
 import android.util.IndentingPrintWriter;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -142,6 +144,7 @@
     }
 
     @Override
+    @NonNull
     public ExpandableViewState createExpandableViewState() {
         return new FooterViewState();
     }
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 79d883b3..1fb265f 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
@@ -4460,8 +4460,7 @@
     }
 
     private void updateVisibility() {
-        boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard();
-        setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
+        mController.updateVisibility(!mAmbientState.isFullyHidden() || !onKeyguard());
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4526,17 +4525,21 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void updateEmptyShadeView(boolean visible, boolean notifVisibleInShade) {
+    void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         int oldTextRes = mEmptyShadeView.getTextResource();
-        int newTextRes = notifVisibleInShade
+        int newTextRes = areNotificationsHiddenInShade
                 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
         if (oldTextRes != newTextRes) {
             mEmptyShadeView.setText(newTextRes);
         }
     }
 
+    public boolean isEmptyShadeViewVisible() {
+        return mEmptyShadeView.isVisible();
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
         if (mFooterView == null) {
@@ -6116,9 +6119,6 @@
             }
             return row.canViewBeDismissed();
         }
-        if (v instanceof PeopleHubView) {
-            return ((PeopleHubView) v).getCanSwipe();
-        }
         return false;
     }
 
@@ -6130,9 +6130,6 @@
             }
             return row.canViewBeCleared();
         }
-        if (v instanceof PeopleHubView) {
-            return ((PeopleHubView) v).getCanSwipe();
-        }
         return false;
     }
 
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 3f6586c..ec1e620 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
@@ -87,8 +87,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
 import com.android.systemui.statusbar.notification.collection.PipelineDumper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -179,7 +177,6 @@
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
     private NotificationSwipeHelper mSwipeHelper;
-    private boolean mShowEmptyShadeView;
     @Nullable private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
@@ -633,7 +630,6 @@
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
             CentralSurfaces centralSurfaces,
             ScrimController scrimController,
-            NotificationGroupManagerLegacy legacyGroupManager,
             GroupExpansionManager groupManager,
             @SilentHeader SectionHeaderController silentHeaderController,
             NotifPipeline notifPipeline,
@@ -677,12 +673,6 @@
         mJankMonitor = jankMonitor;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
         mGroupExpansionManager = groupManager;
-        legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
-            @Override
-            public void onGroupsChanged() {
-                mCentralSurfaces.requestNotificationUpdate("onGroupsChanged");
-            }
-        });
         mSilentHeaderController = silentHeaderController;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
@@ -1182,8 +1172,21 @@
     }
 
     /**
-     * Update whether we should show the empty shade view (no notifications in the shade).
-     * If so, send the update to our view.
+     * Set the visibility of the view, and propagate it to specific children.
+     *
+     * @param visible either the view is visible or not.
+     */
+    public void updateVisibility(boolean visible) {
+        mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+
+        if (mView.getVisibility() == View.VISIBLE) {
+            // Synchronize EmptyShadeView visibility with the parent container.
+            updateShowEmptyShadeView();
+        }
+    }
+
+    /**
+     * Update whether we should show the empty shade view ("no notifications" in the shade).
      *
      * When in split mode, notifications are always visible regardless of the state of the
      * QuickSettings panel. That being the case, empty view is always shown if the other conditions
@@ -1191,18 +1194,31 @@
      */
     public void updateShowEmptyShadeView() {
         Trace.beginSection("NSSLC.updateShowEmptyShadeView");
-        mShowEmptyShadeView = mStatusBarStateController.getCurrentOrUpcomingState() != KEYGUARD
-                && !mView.isQsFullScreen()
-                && getVisibleNotificationCount() == 0;
 
-        mView.updateEmptyShadeView(
-                mShowEmptyShadeView,
-                mZenModeController.areNotificationsHiddenInShade());
+        final boolean shouldShow = getVisibleNotificationCount() == 0
+                && !mView.isQsFullScreen()
+                // Hide empty shade view when in transition to Keyguard.
+                // That avoids "No Notifications" to blink when transitioning to AOD.
+                // For more details, see: b/228790482
+                && !isInTransitionToKeyguard();
+
+        mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
+
         Trace.endSection();
     }
 
+    /**
+     * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
+     *         and false otherwise.
+     */
+    private boolean isInTransitionToKeyguard() {
+        final int currentState = mStatusBarStateController.getState();
+        final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState();
+        return (currentState != upcomingState && upcomingState == KEYGUARD);
+    }
+
     public boolean isShowingEmptyShadeView() {
-        return mShowEmptyShadeView;
+        return mView.isEmptyShadeViewVisible();
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
deleted file mode 100644
index b13e7fb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack
-
-import android.annotation.ColorInt
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
-import com.android.systemui.statusbar.notification.people.DataListener
-import com.android.systemui.statusbar.notification.people.PersonViewModel
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
-
-class PeopleHubView(context: Context, attrs: AttributeSet) :
-        StackScrollerDecorView(context, attrs), SwipeableView {
-
-    private lateinit var contents: ViewGroup
-    private lateinit var label: TextView
-
-    lateinit var personViewAdapters: Sequence<DataListener<PersonViewModel?>>
-        private set
-
-    override fun onFinishInflate() {
-        contents = requireViewById(R.id.people_list)
-        label = requireViewById(R.id.header_label)
-        personViewAdapters = (0 until contents.childCount)
-                .asSequence() // so we can map
-                .mapNotNull { idx ->
-                    // get all our people slots
-                    (contents.getChildAt(idx) as? ImageView)?.let(::PersonDataListenerImpl)
-                }
-                .toList() // cache it
-                .asSequence() // but don't reveal it's a list
-        super.onFinishInflate()
-        setVisible(true /* nowVisible */, false /* animate */)
-    }
-
-    fun setTextColor(@ColorInt color: Int) = label.setTextColor(color)
-
-    override fun findContentView(): View = contents
-    override fun findSecondaryView(): View? = null
-
-    override fun hasFinishedInitialization(): Boolean = true
-
-    override fun createMenu(): NotificationMenuRowPlugin? = null
-
-    override fun resetTranslation() {
-        translationX = 0f
-    }
-
-    override fun setTranslation(translation: Float) {
-        if (canSwipe) {
-            super.setTranslation(translation)
-        }
-    }
-
-    var canSwipe: Boolean = false
-        set(value) {
-            if (field != value) {
-                if (field) {
-                    resetTranslation()
-                }
-                field = value
-            }
-        }
-
-    override fun needsClippingToShelf(): Boolean = true
-
-    override fun applyContentTransformation(contentAlpha: Float, translationY: Float) {
-        super.applyContentTransformation(contentAlpha, translationY)
-        for (i in 0 until contents.childCount) {
-            val view = contents.getChildAt(i)
-            view.alpha = contentAlpha
-            view.translationY = translationY
-        }
-    }
-
-    fun setOnHeaderClickListener(listener: OnClickListener) = label.setOnClickListener(listener)
-
-    private inner class PersonDataListenerImpl(val avatarView: ImageView) :
-            DataListener<PersonViewModel?> {
-
-        override fun onDataChanged(data: PersonViewModel?) {
-            avatarView.visibility = data?.let { View.VISIBLE } ?: View.GONE
-            avatarView.setImageDrawable(data?.icon)
-            avatarView.setOnClickListener { data?.onClick?.invoke() }
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 6d513d0da..353355b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -120,11 +120,64 @@
         updateClipping(algorithmState, ambientState);
         updateSpeedBumpState(algorithmState, speedBumpIndex);
         updateShelfState(algorithmState, ambientState);
+        updateAlphaState(algorithmState, ambientState);
         getNotificationChildrenStates(algorithmState, ambientState);
     }
 
+    private void updateAlphaState(StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
+        for (ExpandableView view : algorithmState.visibleChildren) {
+            final ViewState viewState = view.getViewState();
+
+            final boolean isHunGoingToShade = ambientState.isShadeExpanded()
+                    && view == ambientState.getTrackedHeadsUpRow();
+
+            if (isHunGoingToShade) {
+                // Keep 100% opacity for heads up notification going to shade.
+                viewState.alpha = 1f;
+            } else if (ambientState.isOnKeyguard()) {
+                // Adjust alpha for wakeup to lockscreen.
+                viewState.alpha = 1f - ambientState.getHideAmount();
+            } else if (ambientState.isExpansionChanging()) {
+                // Adjust alpha for shade open & close.
+                float expansion = ambientState.getExpansionFraction();
+                viewState.alpha = ambientState.isBouncerInTransit()
+                        ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
+                        : ShadeInterpolation.getContentAlpha(expansion);
+            }
+
+            // For EmptyShadeView if on keyguard, we need to control the alpha to create
+            // a nice transition when the user is dragging down the notification panel.
+            if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
+                final float fractionToShade = ambientState.getFractionToShade();
+                viewState.alpha = ShadeInterpolation.getContentAlpha(fractionToShade);
+            }
+
+            NotificationShelf shelf = ambientState.getShelf();
+            if (shelf != null) {
+                final ViewState shelfState = shelf.getViewState();
+
+                // After the shelf has updated its yTranslation, explicitly set alpha=0 for view
+                // below shelf to skip rendering them in the hardware layer. We do not set them
+                // invisible because that runs invalidate & onDraw when these views return onscreen,
+                // which is more expensive.
+                if (shelfState.hidden) {
+                    // When the shelf is hidden, it won't clip views, so we don't hide rows
+                    return;
+                }
+
+                final float shelfTop = shelfState.yTranslation;
+                final float viewTop = viewState.yTranslation;
+                if (viewTop >= shelfTop) {
+                    viewState.alpha = 0;
+                }
+            }
+        }
+    }
+
     /**
      * How expanded or collapsed notifications are when pulling down the shade.
+     *
      * @param ambientState Current ambient state.
      * @return 0 when fully collapsed, 1 when expanded.
      */
@@ -208,22 +261,6 @@
         }
 
         shelf.updateState(algorithmState, ambientState);
-
-        // After the shelf has updated its yTranslation, explicitly set alpha=0 for view below shelf
-        // to skip rendering them in the hardware layer. We do not set them invisible because that
-        // runs invalidate & onDraw when these views return onscreen, which is more expensive.
-        if (shelf.getViewState().hidden) {
-            // When the shelf is hidden, it won't clip views, so we don't hide rows
-            return;
-        }
-        final float shelfTop = shelf.getViewState().yTranslation;
-
-        for (ExpandableView view : algorithmState.visibleChildren) {
-            final float viewTop = view.getViewState().yTranslation;
-            if (viewTop >= shelfTop) {
-                view.getViewState().alpha = 0;
-            }
-        }
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
@@ -473,21 +510,6 @@
         ExpandableViewState viewState = view.getViewState();
         viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
 
-        final boolean isHunGoingToShade = ambientState.isShadeExpanded()
-                && view == ambientState.getTrackedHeadsUpRow();
-        if (isHunGoingToShade) {
-            // Keep 100% opacity for heads up notification going to shade.
-        } else if (ambientState.isOnKeyguard()) {
-            // Adjust alpha for wakeup to lockscreen.
-            viewState.alpha = 1f - ambientState.getHideAmount();
-        } else if (ambientState.isExpansionChanging()) {
-            // Adjust alpha for shade open & close.
-            float expansion = ambientState.getExpansionFraction();
-            viewState.alpha = ambientState.isBouncerInTransit()
-                    ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
-                    : ShadeInterpolation.getContentAlpha(expansion);
-        }
-
         final float expansionFraction = getExpansionFractionWithoutShelf(
                 algorithmState, ambientState);
 
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 33c1f99..7bef844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -64,7 +65,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -87,7 +87,7 @@
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
-    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 2;
+    private static final int UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
     private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
     private static final VibrationEffect ERROR_VIBRATION_EFFECT =
@@ -450,7 +450,7 @@
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
         Runnable wakeUp = ()-> {
-            if (!wasDeviceInteractive) {
+            if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
                 if (DEBUG_BIO_WAKELOCK) {
                     Log.i(TAG, "bio wakelock: Authenticated, waking up...");
                 }
@@ -653,7 +653,7 @@
 
         if (!mVibratorHelper.hasVibrator()
                 && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
-            startWakeAndUnlock(MODE_SHOW_BOUNCER);
+            startWakeAndUnlock(MODE_ONLY_WAKE);
         } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
             long currUptimeMillis = SystemClock.uptimeMillis();
@@ -664,7 +664,7 @@
             }
             mLastFpFailureUptimeMillis = currUptimeMillis;
 
-            if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+            if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
                 startWakeAndUnlock(MODE_SHOW_BOUNCER);
                 UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
                 mNumConsecutiveFpFailures = 0;
@@ -690,13 +690,13 @@
         Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
 
-        // if we're on the shade and we're locked out, immediately show the bouncer
-        if (biometricSourceType == BiometricSourceType.FINGERPRINT
+        final boolean fingerprintLockout = biometricSourceType == BiometricSourceType.FINGERPRINT
                 && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
-                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
-                && mUpdateMonitor.isUdfpsSupported()
-                && (mStatusBarStateController.getState() == StatusBarState.SHADE
-                    || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
+                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
+        final boolean faceLockout = biometricSourceType == BiometricSourceType.FACE
+                && (msgId == FaceManager.FACE_ERROR_LOCKOUT
+                || msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT);
+        if (fingerprintLockout || faceLockout) {
             startWakeAndUnlock(MODE_SHOW_BOUNCER);
             UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 747c4de..52a45d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -55,7 +55,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -326,12 +325,12 @@
                 mNotificationPanelViewController.expand(true /* animate */);
                 mNotificationStackScrollLayoutController.setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
-                mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
+                mMetricsLogger.count("panel_open", 1);
             } else if (!mNotificationPanelViewController.isInSettings()
                     && !mNotificationPanelViewController.isExpanding()) {
                 mNotificationPanelViewController.flingSettings(0 /* velocity */,
-                        NotificationPanelView.FLING_EXPAND);
-                mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
+                        NotificationPanelViewController.FLING_EXPAND);
+                mMetricsLogger.count("panel_open_qs", 1);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index cb2ca51..a190485 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,8 +31,7 @@
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-import static com.android.systemui.charging.WirelessChargingRippleControllerKt.DEFAULT_DURATION;
-import static com.android.systemui.charging.WirelessChargingRippleControllerKt.UNKNOWN_BATTERY_LEVEL;
+import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -139,8 +138,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
 import com.android.systemui.charging.WiredChargingRippleController;
-import com.android.systemui.charging.WirelessChargingRippleController;
-import com.android.systemui.charging.WirelessChargingView;
+import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
@@ -509,7 +507,6 @@
     private final WallpaperManager mWallpaperManager;
 
     private CentralSurfacesComponent mCentralSurfacesComponent;
-    private WirelessChargingRippleController mWirelessChargingRippleController;
 
     // Flags for disabling the status bar
     // Two variables becaseu the first one evidently ran out of room for new flags.
@@ -735,7 +732,6 @@
             InteractionJankMonitor jankMonitor,
             DeviceStateManager deviceStateManager,
             WiredChargingRippleController wiredChargingRippleController,
-            WirelessChargingRippleController wirelessChargingRippleController,
             IDreamManager dreamManager) {
         super(context);
         mNotificationsController = notificationsController;
@@ -853,7 +849,6 @@
         deviceStateManager.registerCallback(mMainExecutor,
                 new FoldStateListener(mContext, this::onFoldedStateChanged));
         wiredChargingRippleController.registerCallbacks();
-        mWirelessChargingRippleController = wirelessChargingRippleController;
     }
 
     @Override
@@ -2167,11 +2162,9 @@
 
     protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,
             long animationDelay) {
-        WirelessChargingView wirelessChargingView = WirelessChargingView.create(mContext,
-                transmittingBatteryLevel, batteryLevel, /* isDozing= */ false,
-                RippleShape.CIRCLE, DEFAULT_DURATION);
-        mWirelessChargingRippleController.show(wirelessChargingView, animationDelay,
-                new WirelessChargingRippleController.Callback() {
+        WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+                transmittingBatteryLevel, batteryLevel,
+                new WirelessChargingAnimation.Callback() {
                     @Override
                     public void onAnimationStarting() {
                         mNotificationShadeWindowController.setRequestTopUi(true, TAG);
@@ -2181,7 +2174,8 @@
                     public void onAnimationEnded() {
                         mNotificationShadeWindowController.setRequestTopUi(false, TAG);
                     }
-                });
+                }, /* isDozing= */ false, RippleShape.CIRCLE,
+                sUiEventLogger).show(animationDelay);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ed186ab..8273d57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -33,7 +33,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
@@ -137,11 +136,12 @@
                 LinearLayout linearLayout,
                 FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider) {
+                Provider<WifiViewModel> wifiViewModelProvider,
+                DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
-            mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
+            mDarkIconDispatcher = darkIconDispatcher;
         }
 
         @Override
@@ -198,20 +198,24 @@
             private final FeatureFlags mFeatureFlags;
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final Provider<WifiViewModel> mWifiViewModelProvider;
+            private final DarkIconDispatcher mDarkIconDispatcher;
 
             @Inject
             public Factory(
                     FeatureFlags featureFlags,
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    Provider<WifiViewModel> wifiViewModelProvider) {
+                    Provider<WifiViewModel> wifiViewModelProvider,
+                    DarkIconDispatcher darkIconDispatcher) {
                 mFeatureFlags = featureFlags;
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiViewModelProvider = wifiViewModelProvider;
+                mDarkIconDispatcher = darkIconDispatcher;
             }
 
             public DarkIconManager create(LinearLayout group) {
                 return new DarkIconManager(
-                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
+                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider,
+                        mDarkIconDispatcher);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 5566fa6..c96faab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -16,13 +16,23 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.model
 
+import androidx.annotation.VisibleForTesting
+
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel {
     /** A model representing that we have no active wifi network. */
     object Inactive : WifiNetworkModel()
 
+    /**
+     * A model representing that our wifi network is actually a carrier merged network, meaning it's
+     * treated as more of a mobile network.
+     *
+     * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
+     */
+    object CarrierMerged : WifiNetworkModel()
+
     /** Provides information about an active wifi network. */
-    class Active(
+    data class Active(
         /**
          * The [android.net.Network.netId] we received from
          * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network.
@@ -31,6 +41,19 @@
          */
         val networkId: Int,
 
+        /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */
+        val isValidated: Boolean = false,
+
+        /**
+         * The wifi signal level, guaranteed to be 0 <= level <= 4.
+         *
+         * Null if we couldn't fetch the level for some reason.
+         *
+         * TODO(b/238425913): The level will only be null if we have a null WifiManager. Is there a
+         *   way we can guarantee a non-null WifiManager?
+         */
+        val level: Int? = null,
+
         /** See [android.net.wifi.WifiInfo.ssid]. */
         val ssid: String? = null,
 
@@ -42,5 +65,18 @@
 
         /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
         val passpointProviderFriendlyName: String? = null,
-    ) : WifiNetworkModel()
+    ) : WifiNetworkModel() {
+        init {
+            require(level == null || level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
+                "0 <= wifi level <= 4 required; level was $level"
+            }
+        }
+
+        companion object {
+            @VisibleForTesting
+            internal const val MIN_VALID_LEVEL = 0
+            @VisibleForTesting
+            internal const val MAX_VALID_LEVEL = 4
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 43fbabc..103f3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -21,6 +21,7 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkRequest
@@ -31,6 +32,7 @@
 import com.android.settingslib.Utils
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
@@ -38,10 +40,13 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Provides data related to the wifi state.
@@ -63,10 +68,11 @@
 @SysUISingleton
 @SuppressLint("MissingPermission")
 class WifiRepositoryImpl @Inject constructor(
-        connectivityManager: ConnectivityManager,
-        wifiManager: WifiManager?,
-        @Main mainExecutor: Executor,
-        logger: ConnectivityPipelineLogger,
+    connectivityManager: ConnectivityManager,
+    logger: ConnectivityPipelineLogger,
+    @Main mainExecutor: Executor,
+    @Application scope: CoroutineScope,
+    wifiManager: WifiManager?,
 ) : WifiRepository {
     override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
         var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
@@ -80,7 +86,12 @@
 
                 val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
                 if (wifiInfo?.isPrimary == true) {
-                    val wifiNetworkModel = wifiInfoToModel(wifiInfo, network.getNetId())
+                    val wifiNetworkModel = createWifiNetworkModel(
+                        wifiInfo,
+                        network,
+                        networkCapabilities,
+                        wifiManager,
+                    )
                     logger.logTransformation(
                         WIFI_NETWORK_CALLBACK_NAME,
                         oldValue = currentWifi,
@@ -107,11 +118,19 @@
             }
         }
 
-        trySend(WIFI_NETWORK_DEFAULT)
         connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
 
         awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
     }
+        // There will be multiple wifi icons in different places that will frequently
+        // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
+        // new subscribes will get the latest value immediately upon subscription. Otherwise, the
+        // views could show stale data. See b/244173280.
+        .stateIn(
+            scope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = WIFI_NETWORK_DEFAULT
+        )
 
     override val wifiActivity: Flow<WifiActivityModel> =
             if (wifiManager == null) {
@@ -164,14 +183,25 @@
             }
         }
 
-        private fun wifiInfoToModel(wifiInfo: WifiInfo, networkId: Int): WifiNetworkModel {
-            return WifiNetworkModel.Active(
-                networkId,
-                wifiInfo.ssid,
-                wifiInfo.isPasspointAp,
-                wifiInfo.isOsuAp,
-                wifiInfo.passpointProviderFriendlyName
-            )
+        private fun createWifiNetworkModel(
+            wifiInfo: WifiInfo,
+            network: Network,
+            networkCapabilities: NetworkCapabilities,
+            wifiManager: WifiManager?,
+        ): WifiNetworkModel {
+            return if (wifiInfo.isCarrierMerged) {
+                WifiNetworkModel.CarrierMerged
+            } else {
+                WifiNetworkModel.Active(
+                        network.getNetId(),
+                        isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
+                        level = wifiManager?.calculateSignalLevel(wifiInfo.rssi),
+                        wifiInfo.ssid,
+                        wifiInfo.isPasspointAp,
+                        wifiInfo.isOsuAp,
+                        wifiInfo.passpointProviderFriendlyName
+                )
+            }
         }
 
         private fun prettyPrintActivity(activity: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index a9da63b..afe19af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -33,11 +33,12 @@
  */
 @SysUISingleton
 class WifiInteractor @Inject constructor(
-        repository: WifiRepository,
+    repository: WifiRepository,
 ) {
     private val ssid: Flow<String?> = repository.wifiNetwork.map { info ->
         when (info) {
             is WifiNetworkModel.Inactive -> null
+            is WifiNetworkModel.CarrierMerged -> null
             is WifiNetworkModel.Active -> when {
                 info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
                     info.passpointProviderFriendlyName
@@ -47,6 +48,10 @@
         }
     }
 
+    /** Our current wifi network. See [WifiNetworkModel]. */
+    val wifiNetwork: Flow<WifiNetworkModel> = repository.wifiNetwork
+
+    /** True if our wifi network has activity in (download), and false otherwise. */
     val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
             activity.hasActivityIn && ssid != null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index b06aaf4..7607ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -25,10 +25,10 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
 /**
@@ -50,11 +50,22 @@
 
         view.isVisible = true
         iconView.isVisible = true
-        iconView.setImageDrawable(view.context.getDrawable(WIFI_FULL_ICONS[2]))
 
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
+                    viewModel.wifiIconResId.distinctUntilChanged().collect { iconResId ->
+                        iconView.setImageDrawable(
+                            if (iconResId != null && iconResId > 0) {
+                                iconView.context.getDrawable(iconResId)
+                            } else {
+                                null
+                            }
+                        )
+                    }
+                }
+
+                launch {
                     viewModel.tint.collect { tint ->
                         iconView.imageTintList = ColorStateList.valueOf(tint)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 7a26260..4fdcc44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,20 +17,24 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
+import androidx.annotation.DrawableRes
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /**
  * Models the UI state for the status bar wifi icon.
- *
- * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
  */
 class WifiViewModel @Inject constructor(
     statusBarPipelineFlags: StatusBarPipelineFlags,
@@ -38,6 +42,23 @@
     private val logger: ConnectivityPipelineLogger,
     private val interactor: WifiInteractor,
 ) {
+    /**
+     * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
+     */
+    @DrawableRes
+    val wifiIconResId: Flow<Int?> = interactor.wifiNetwork.map {
+        when (it) {
+            is WifiNetworkModel.CarrierMerged -> null
+            is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
+            is WifiNetworkModel.Active ->
+                when {
+                    it.level == null -> null
+                    it.isValidated -> WIFI_FULL_ICONS[it.level]
+                    else -> WIFI_NO_INTERNET_ICONS[it.level]
+                }
+        }
+    }
+
     val isActivityInVisible: Flow<Boolean>
         get() =
             if (!constants.shouldShowActivityConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
new file mode 100644
index 0000000..7baebf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.zip
+
+/**
+ * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
+ * Note that the new Flow will not start emitting until it has received two emissions from the
+ * upstream Flow.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T, R> Flow<T>.pairwiseBy(transform: suspend (old: T, new: T) -> R): Flow<R> {
+    // same as current flow, but with the very first event skipped
+    val nextEvents = drop(1)
+    // zip current flow and nextEvents; transform will receive a pair of old and new value. This
+    // works because zip will suppress emissions until both flows have emitted something; since in
+    // this case both flows are emitting at the same rate, but the current flow just has one extra
+    // thing emitted at the start, the effect is that zip will cache the most recent value while
+    // waiting for the next emission from nextEvents.
+    return zip(nextEvents, transform)
+}
+
+/**
+ * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
+ * [initialValue] will be used as the "old" value for the first emission.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T, R> Flow<T>.pairwiseBy(
+    initialValue: T,
+    transform: suspend (previousValue: T, newValue: T) -> R,
+): Flow<R> =
+    onStart { emit(initialValue) }.pairwiseBy(transform)
+
+/**
+ * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
+ * Flow will not start emitting until it has received two emissions from the upstream Flow.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
+
+/**
+ * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue]
+ * will be used as the "old" value for the first emission.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T> Flow<T>.pairwise(initialValue: T): Flow<WithPrev<T>> = pairwiseBy(initialValue, ::WithPrev)
+
+/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
+data class WithPrev<T>(val previousValue: T, val newValue: T)
+
+/**
+ * Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
+ * [transform].
+ */
+fun <T, R> Flow<Set<T>>.setChangesBy(
+    transform: suspend (removed: Set<T>, added: Set<T>) -> R,
+): Flow<R> = onStart { emit(emptySet()) }.distinctUntilChanged()
+    .pairwiseBy { old: Set<T>, new: Set<T> ->
+        // If an element was present in the old set, but not the new one, then it was removed
+        val removed = old - new
+        // If an element is present in the new set, but on the old one, then it was added
+        val added = new - old
+        transform(removed, added)
+    }
+
+/** Returns a new [Flow] that produces the [Set] changes between each emission from [this]. */
+fun <T> Flow<Set<T>>.setChanges(): Flow<SetChanges<T>> = setChangesBy(::SetChanges)
+
+/** Contains the difference in elements between two [Set]s. */
+data class SetChanges<T>(
+    /** Elements that are present in the first [Set] but not in the second. */
+    val removed: Set<T>,
+    /** Elements that are present in the second [Set] but not in the first. */
+    val added: Set<T>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 13c3df3..c7ba518 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -48,7 +48,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -248,6 +247,7 @@
 
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final VolumePanelFactory mVolumePanelFactory;
     private final ActivityStarter mActivityStarter;
 
     private boolean mShowing;
@@ -279,6 +279,7 @@
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
             MediaOutputDialogFactory mediaOutputDialogFactory,
+            VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor) {
         mContext =
@@ -290,6 +291,7 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mVolumePanelFactory = volumePanelFactory;
         mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -1045,10 +1047,9 @@
         if (mSettingsIcon != null) {
             mSettingsIcon.setOnClickListener(v -> {
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
-                Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
                 mMediaOutputDialogFactory.dismiss();
-                mActivityStarter.startActivity(intent, true /* dismissShade */);
+                mVolumePanelFactory.create(true /* aboveStatusBar */, null);
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
new file mode 100644
index 0000000..2c74fb9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.provider.SettingsSlicesContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceMetadata;
+import androidx.slice.widget.EventInfo;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.MediaOutputConstants;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Visual presentation of the volume panel dialog.
+ */
+public class VolumePanelDialog extends SystemUIDialog implements LifecycleOwner {
+    private static final String TAG = "VolumePanelDialog";
+
+    private static final int DURATION_SLICE_BINDING_TIMEOUT_MS = 200;
+    private static final int DEFAULT_SLICE_SIZE = 4;
+
+    private RecyclerView mVolumePanelSlices;
+    private VolumePanelSlicesAdapter mVolumePanelSlicesAdapter;
+    private final LifecycleRegistry mLifecycleRegistry;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Map<Uri, LiveData<Slice>> mSliceLiveData = new LinkedHashMap<>();
+    private final HashSet<Uri> mLoadedSlices = new HashSet<>();
+    private boolean mSlicesReadyToLoad;
+    private LocalBluetoothProfileManager mProfileManager;
+
+    public VolumePanelDialog(Context context, boolean aboveStatusBar) {
+        super(context);
+        mLifecycleRegistry = new LifecycleRegistry(this);
+        if (!aboveStatusBar) {
+            getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.d(TAG, "onCreate");
+
+        View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.volume_panel_dialog,
+                null);
+        final Window window = getWindow();
+        window.setContentView(dialogView);
+
+        Button doneButton = dialogView.findViewById(R.id.done_button);
+        doneButton.setOnClickListener(v -> dismiss());
+        Button settingsButton = dialogView.findViewById(R.id.settings_button);
+        settingsButton.setOnClickListener(v -> {
+            getContext().startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK));
+            dismiss();
+        });
+
+        LocalBluetoothManager localBluetoothManager = LocalBluetoothManager.getInstance(
+                getContext(), null);
+        if (localBluetoothManager != null) {
+            mProfileManager = localBluetoothManager.getProfileManager();
+        }
+
+        mVolumePanelSlices = dialogView.findViewById(R.id.volume_panel_parent_layout);
+        mVolumePanelSlices.setLayoutManager(new LinearLayoutManager(getContext()));
+
+        loadAllSlices();
+
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+    }
+
+    private void loadAllSlices() {
+        mSliceLiveData.clear();
+        mLoadedSlices.clear();
+        final List<Uri> sliceUris = getSlices();
+
+        for (Uri uri : sliceUris) {
+            final LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(getContext(), uri,
+                    (int type, Throwable source) -> {
+                        if (!removeSliceLiveData(uri)) {
+                            mLoadedSlices.add(uri);
+                        }
+                    });
+
+            // Add slice first to make it in order.  Will remove it later if there's an error.
+            mSliceLiveData.put(uri, sliceLiveData);
+
+            sliceLiveData.observe(this, slice -> {
+                if (mLoadedSlices.contains(uri)) {
+                    return;
+                }
+                Log.d(TAG, "received slice: " + (slice == null ? null : slice.getUri()));
+                final SliceMetadata metadata = SliceMetadata.from(getContext(), slice);
+                if (slice == null || metadata.isErrorSlice()) {
+                    if (!removeSliceLiveData(uri)) {
+                        mLoadedSlices.add(uri);
+                    }
+                } else if (metadata.getLoadingState() == SliceMetadata.LOADED_ALL) {
+                    mLoadedSlices.add(uri);
+                } else {
+                    mHandler.postDelayed(() -> {
+                        mLoadedSlices.add(uri);
+                        setupAdapterWhenReady();
+                    }, DURATION_SLICE_BINDING_TIMEOUT_MS);
+                }
+
+                setupAdapterWhenReady();
+            });
+        }
+    }
+
+    private void setupAdapterWhenReady() {
+        if (mLoadedSlices.size() == mSliceLiveData.size() && !mSlicesReadyToLoad) {
+            mSlicesReadyToLoad = true;
+            mVolumePanelSlicesAdapter = new VolumePanelSlicesAdapter(this, mSliceLiveData);
+            mVolumePanelSlicesAdapter.setOnSliceActionListener((eventInfo, sliceItem) -> {
+                if (eventInfo.actionType == EventInfo.ACTION_TYPE_SLIDER) {
+                    return;
+                }
+                this.dismiss();
+            });
+            if (mSliceLiveData.size() < DEFAULT_SLICE_SIZE) {
+                mVolumePanelSlices.setMinimumHeight(0);
+            }
+            mVolumePanelSlices.setAdapter(mVolumePanelSlicesAdapter);
+        }
+    }
+
+    private boolean removeSliceLiveData(Uri uri) {
+        boolean removed = false;
+        // Keeps observe media output slice
+        if (!uri.equals(MEDIA_OUTPUT_INDICATOR_SLICE_URI)) {
+            Log.d(TAG, "remove uri: " + uri);
+            removed = mSliceLiveData.remove(uri) != null;
+            if (mVolumePanelSlicesAdapter != null) {
+                mVolumePanelSlicesAdapter.updateDataSet(new ArrayList<>(mSliceLiveData.values()));
+            }
+        }
+        return removed;
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Log.d(TAG, "onStart");
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Log.d(TAG, "onStop");
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+    }
+
+    private List<Uri> getSlices() {
+        final List<Uri> uris = new ArrayList<>();
+        uris.add(REMOTE_MEDIA_SLICE_URI);
+        uris.add(VOLUME_MEDIA_URI);
+        Uri controlUri = getExtraControlUri();
+        if (controlUri != null) {
+            Log.d(TAG, "add extra control slice");
+            uris.add(controlUri);
+        }
+        uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
+        uris.add(VOLUME_CALL_URI);
+        uris.add(VOLUME_RINGER_URI);
+        uris.add(VOLUME_ALARM_URI);
+        return uris;
+    }
+
+    private static final String SETTINGS_SLICE_AUTHORITY = "com.android.settings.slices";
+    private static final Uri REMOTE_MEDIA_SLICE_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath(MediaOutputConstants.KEY_REMOTE_MEDIA)
+            .build();
+    private static final Uri VOLUME_MEDIA_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath("media_volume")
+            .build();
+    private static final Uri MEDIA_OUTPUT_INDICATOR_SLICE_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
+            .appendPath("media_output_indicator")
+            .build();
+    private static final Uri VOLUME_CALL_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath("call_volume")
+            .build();
+    private static final Uri VOLUME_RINGER_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath("ring_volume")
+            .build();
+    private static final Uri VOLUME_ALARM_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SETTINGS_SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath("alarm_volume")
+            .build();
+
+    private Uri getExtraControlUri() {
+        Uri controlUri = null;
+        final BluetoothDevice bluetoothDevice = findActiveDevice();
+        if (bluetoothDevice != null) {
+            // The control slice width = dialog width - horizontal padding of two sides
+            final int dialogWidth =
+                    getWindow().getWindowManager().getCurrentWindowMetrics().getBounds().width();
+            final int controlSliceWidth = dialogWidth
+                    - getContext().getResources().getDimensionPixelSize(
+                    R.dimen.volume_panel_slice_horizontal_padding) * 2;
+            final String uri = BluetoothUtils.getControlUriMetaData(bluetoothDevice);
+            if (!TextUtils.isEmpty(uri)) {
+                try {
+                    controlUri = Uri.parse(uri + controlSliceWidth);
+                } catch (NullPointerException exception) {
+                    Log.d(TAG, "unable to parse extra control uri");
+                    controlUri = null;
+                }
+            }
+        }
+        return controlUri;
+    }
+
+    private BluetoothDevice findActiveDevice() {
+        if (mProfileManager != null) {
+            final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+            if (a2dpProfile != null) {
+                return a2dpProfile.getActiveDevice();
+            }
+        }
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycleRegistry;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
new file mode 100644
index 0000000..f11d5d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.Log
+import javax.inject.Inject
+
+private const val TAG = "VolumePanelDialogReceiver"
+private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
+private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
+
+/**
+ * BroadcastReceiver for handling volume panel dialog intent
+ */
+class VolumePanelDialogReceiver @Inject constructor(
+    private val volumePanelFactory: VolumePanelFactory
+) : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent) {
+        Log.d(TAG, "onReceive intent" + intent.action)
+        if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
+                TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
+            volumePanelFactory.create(true, null)
+        } else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
+            volumePanelFactory.dismiss()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
new file mode 100644
index 0000000..c2fafbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+private const val TAG = "VolumePanelFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [VolumePanelDialog] objects. This is the dialog that allows the user to adjust
+ * multiple streams with sliders.
+ */
+@SysUISingleton
+class VolumePanelFactory @Inject constructor(
+    private val context: Context,
+    private val dialogLaunchAnimator: DialogLaunchAnimator
+) {
+    companion object {
+        var volumePanelDialog: VolumePanelDialog? = null
+    }
+
+    /** Creates a [VolumePanelDialog]. The dialog will be animated from [view] if it is not null. */
+    fun create(aboveStatusBar: Boolean, view: View? = null) {
+        if (volumePanelDialog?.isShowing == true) {
+            return
+        }
+
+        val dialog = VolumePanelDialog(context, aboveStatusBar)
+        volumePanelDialog = dialog
+
+        // Show the dialog.
+        if (view != null) {
+            dialogLaunchAnimator.showFromView(dialog, view, animateBackgroundBoundsChange = true)
+        } else {
+            dialog.show()
+        }
+    }
+
+    /** Dismiss [VolumePanelDialog] if exist. */
+    fun dismiss() {
+        if (DEBUG) {
+            Log.d(TAG, "dismiss dialog")
+        }
+        volumePanelDialog?.dismiss()
+        volumePanelDialog = null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java
new file mode 100644
index 0000000..2371402
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import static android.app.slice.Slice.HINT_ERROR;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.widget.SliceView;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RecyclerView adapter for Slices in Settings Panels.
+ */
+public class VolumePanelSlicesAdapter extends
+        RecyclerView.Adapter<VolumePanelSlicesAdapter.SliceRowViewHolder> {
+
+    private final List<LiveData<Slice>> mSliceLiveData;
+    private final LifecycleOwner mLifecycleOwner;
+    private SliceView.OnSliceActionListener mOnSliceActionListener;
+
+    public VolumePanelSlicesAdapter(LifecycleOwner lifecycleOwner,
+            Map<Uri, LiveData<Slice>> sliceLiveData) {
+        mLifecycleOwner = lifecycleOwner;
+        mSliceLiveData = new ArrayList<>(sliceLiveData.values());
+    }
+
+    @NonNull
+    @Override
+    public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
+        final Context context = viewGroup.getContext();
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        View view = inflater.inflate(R.layout.volume_panel_slice_slider_row, viewGroup, false);
+        return new SliceRowViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
+        sliceRowViewHolder.onBind(mSliceLiveData.get(position), position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSliceLiveData.size();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return position;
+    }
+
+    void setOnSliceActionListener(SliceView.OnSliceActionListener listener) {
+        mOnSliceActionListener = listener;
+    }
+
+    void updateDataSet(ArrayList<LiveData<Slice>> list) {
+        mSliceLiveData.clear();
+        mSliceLiveData.addAll(list);
+        notifyDataSetChanged();
+    }
+
+    /**
+     * ViewHolder for binding Slices to SliceViews.
+     */
+    public class SliceRowViewHolder extends RecyclerView.ViewHolder {
+
+        private final SliceView mSliceView;
+
+        public SliceRowViewHolder(View view) {
+            super(view);
+            mSliceView = view.findViewById(R.id.slice_view);
+            mSliceView.setMode(SliceView.MODE_LARGE);
+            mSliceView.setShowTitleItems(true);
+            mSliceView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+            mSliceView.setOnSliceActionListener(mOnSliceActionListener);
+        }
+
+        /**
+         * Called when the view is displayed.
+         */
+        public void onBind(LiveData<Slice> sliceLiveData, int position) {
+            sliceLiveData.observe(mLifecycleOwner, mSliceView);
+
+            // Do not show the divider above media devices switcher slice per request
+            final Slice slice = sliceLiveData.getValue();
+
+            // Hides slice which reports with error hint or not contain any slice sub-item.
+            if (slice == null || !isValidSlice(slice)) {
+                mSliceView.setVisibility(View.GONE);
+            } else {
+                mSliceView.setVisibility(View.VISIBLE);
+            }
+        }
+
+        private boolean isValidSlice(Slice slice) {
+            if (slice.getHints().contains(HINT_ERROR)) {
+                return false;
+            }
+            for (SliceItem item : slice.getItems()) {
+                if (item.getFormat().equals(FORMAT_SLICE)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index f3855bd..c5792b9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,6 +30,7 @@
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
+import com.android.systemui.volume.VolumePanelFactory;
 
 import dagger.Binds;
 import dagger.Module;
@@ -52,6 +53,7 @@
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
             MediaOutputDialogFactory mediaOutputDialogFactory,
+            VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
@@ -61,6 +63,7 @@
                 deviceProvisionedController,
                 configurationController,
                 mediaOutputDialogFactory,
+                volumePanelFactory,
                 activityStarter,
                 interactionJankMonitor);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 199048e..ce96741 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -62,7 +62,6 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.BubbleCoordinator;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 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;
@@ -103,7 +102,6 @@
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final NotificationLockscreenUserManager mNotifUserManager;
-    private final NotificationGroupManagerLegacy mNotificationGroupManager;
     private final CommonNotifCollection mCommonNotifCollection;
     private final NotifPipeline mNotifPipeline;
     private final Executor mSysuiMainExecutor;
@@ -130,7 +128,6 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManagerLegacy groupManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
@@ -148,7 +145,6 @@
                     interruptionStateProvider,
                     zenModeController,
                     notifUserManager,
-                    groupManager,
                     notifCollection,
                     notifPipeline,
                     sysUiState,
@@ -171,7 +167,6 @@
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManagerLegacy groupManager,
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
@@ -185,7 +180,6 @@
         mVisibilityProvider = visibilityProvider;
         mNotificationInterruptStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
-        mNotificationGroupManager = groupManager;
         mCommonNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
         mSysuiMainExecutor = sysuiMainExecutor;
@@ -331,16 +325,6 @@
             }
 
             @Override
-            public void removeNotificationEntry(String key) {
-                sysuiMainExecutor.execute(() -> {
-                    final NotificationEntry entry = mCommonNotifCollection.getEntry(key);
-                    if (entry != null) {
-                        mNotificationGroupManager.onEntryRemoved(entry);
-                    }
-                });
-            }
-
-            @Override
             public void updateNotificationBubbleButton(String key) {
                 sysuiMainExecutor.execute(() -> {
                     final NotificationEntry entry = mCommonNotifCollection.getEntry(key);
@@ -351,16 +335,6 @@
             }
 
             @Override
-            public void updateNotificationSuppression(String key) {
-                sysuiMainExecutor.execute(() -> {
-                    final NotificationEntry entry = mCommonNotifCollection.getEntry(key);
-                    if (entry != null) {
-                        mNotificationGroupManager.updateSuppression(entry);
-                    }
-                });
-            }
-
-            @Override
             public void onStackExpandChanged(boolean shouldExpand) {
                 sysuiMainExecutor.execute(() -> {
                     sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
@@ -532,7 +506,10 @@
                                     REASON_GROUP_SUMMARY_CANCELED);
                         }
                     } else {
-                        mNotificationGroupManager.onEntryRemoved(entry);
+                        for (NotifCallback cb : mCallbacks) {
+                            cb.removeNotification(entry, getDismissedByUserStats(entry, true),
+                                    REASON_GROUP_SUMMARY_CANCELED);
+                        }
                     }
                 }, mSysuiMainExecutor);
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index aecec9d..d68e8bd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -387,6 +387,33 @@
     }
 
     @Test
+    public void onResume_sideFpsHintShouldBeShown_sideFpsHintShown() {
+        setupGetSecurityView();
+        setupConditionsToEnableSideFpsHint();
+        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+        reset(mSidefpsController);
+
+        mKeyguardSecurityContainerController.onResume(0);
+
+        verify(mSidefpsController).show();
+        verify(mSidefpsController, never()).hide();
+    }
+
+    @Test
+    public void onResume_sideFpsHintShouldNotBeShown_sideFpsHintHidden() {
+        setupGetSecurityView();
+        setupConditionsToEnableSideFpsHint();
+        setSideFpsHintEnabledFromResources(false);
+        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+        reset(mSidefpsController);
+
+        mKeyguardSecurityContainerController.onResume(0);
+
+        verify(mSidefpsController).hide();
+        verify(mSidefpsController, never()).show();
+    }
+
+    @Test
     public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
         // GIVEN the current security method is SimPin
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
new file mode 100644
index 0000000..6b1ef38
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -0,0 +1,179 @@
+package com.android.systemui
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flag
+import com.android.systemui.flags.FlagListenable
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ChooserSelectorTest : SysuiTestCase() {
+
+    private val flagListener = kotlinArgumentCaptor<FlagListenable.Listener>()
+
+    private val testDispatcher = TestCoroutineDispatcher()
+    private val testScope = CoroutineScope(testDispatcher)
+
+    private lateinit var chooserSelector: ChooserSelector
+
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockPackageManager: PackageManager
+    @Mock private lateinit var mockResources: Resources
+    @Mock private lateinit var mockFeatureFlags: FeatureFlags
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(mockContext.packageManager).thenReturn(mockPackageManager)
+        `when`(mockContext.resources).thenReturn(mockResources)
+        `when`(mockResources.getString(anyInt())).thenReturn(
+                ComponentName("TestPackage", "TestClass").flattenToString())
+
+        chooserSelector = ChooserSelector(mockContext, mockFeatureFlags, testScope, testDispatcher)
+    }
+
+    @After
+    fun tearDown() {
+        testDispatcher.cleanupTestCoroutines()
+    }
+
+    @Test
+    fun initialize_registersFlagListenerUntilScopeCancelled() {
+        // Arrange
+
+        // Act
+        chooserSelector.start()
+
+        // Assert
+        verify(mockFeatureFlags).addListener(
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+        verify(mockFeatureFlags, never()).removeListener(any())
+
+        // Act
+        testScope.cancel()
+
+        // Assert
+        verify(mockFeatureFlags).removeListener(eq(flagListener.value))
+    }
+
+    @Test
+    fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
+        // Arrange
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+
+        // Act
+        chooserSelector.start()
+
+        // Assert
+        verify(mockPackageManager).setComponentEnabledSetting(
+                eq(ComponentName("TestPackage", "TestClass")),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                anyInt())
+    }
+
+    @Test
+    fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
+        // Arrange
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+
+        // Act
+        chooserSelector.start()
+
+        // Assert
+        verify(mockPackageManager).setComponentEnabledSetting(
+                eq(ComponentName("TestPackage", "TestClass")),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                anyInt())
+    }
+
+    @Test
+    fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
+        // Arrange
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        chooserSelector.start()
+        verify(mockFeatureFlags).addListener(
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+        verify(mockPackageManager, never()).setComponentEnabledSetting(
+                any(), eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED), anyInt())
+
+        // Act
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+
+        // Assert
+        verify(mockPackageManager).setComponentEnabledSetting(
+                eq(ComponentName("TestPackage", "TestClass")),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                anyInt())
+    }
+
+    @Test
+    fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
+        // Arrange
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+        chooserSelector.start()
+        verify(mockFeatureFlags).addListener(
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+        verify(mockPackageManager, never()).setComponentEnabledSetting(
+                any(), eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), anyInt())
+
+        // Act
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+
+        // Assert
+        verify(mockPackageManager).setComponentEnabledSetting(
+                eq(ComponentName("TestPackage", "TestClass")),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                anyInt())
+    }
+
+    @Test
+    fun doesNothing_whenAnotherFlagChanges() {
+        // Arrange
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        chooserSelector.start()
+        verify(mockFeatureFlags).addListener(
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+        clearInvocations(mockPackageManager)
+
+        // Act
+        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
+
+        // Assert
+        verifyZeroInteractions(mockPackageManager)
+    }
+
+    private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
+        override fun requestNoRestart() {}
+    }
+}
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 e0d1f7a..b18b0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -20,6 +20,8 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -157,13 +159,15 @@
     @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Captor
-    ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
+    private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
     @Captor
-    ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
+    private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
     @Captor
-    ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
+    private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
     @Captor
-    ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
+    private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
+    @Captor
+    private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefullnessObserverCaptor;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -242,7 +246,9 @@
                 mFaceAuthenticatorsRegisteredCaptor.capture());
 
         when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+        verify(mWakefulnessLifecycle).addObserver(mWakefullnessObserverCaptor.capture());
 
         mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
@@ -759,16 +765,37 @@
     }
 
     @Test
-    public void testForwardsDozeEvent() throws RemoteException {
+    public void testForwardsDozeEvents() throws RemoteException {
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
         mAuthController.setBiometicContextListener(mContextListener);
 
-        mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
         mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
+        mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
 
         InOrder order = inOrder(mContextListener);
-        // invoked twice since the initial state is false
-        order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
-        order.verify(mContextListener).onDozeChanged(eq(true));
+        order.verify(mContextListener, times(2)).onDozeChanged(eq(true), eq(true));
+        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
+        order.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testForwardsWakeEvents() throws RemoteException {
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
+        mAuthController.setBiometicContextListener(mContextListener);
+
+        mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep();
+        mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep();
+        mWakefullnessObserverCaptor.getValue().onStartedWakingUp();
+        mWakefullnessObserverCaptor.getValue().onFinishedWakingUp();
+        mWakefullnessObserverCaptor.getValue().onPostFinishedWakingUp();
+
+        InOrder order = inOrder(mContextListener);
+        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
+        order.verify(mContextListener).onDozeChanged(eq(false), eq(false));
+        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
+        order.verifyNoMoreInteractions();
     }
 
     // Helpers
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt
deleted file mode 100644
index d967b59..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.charging
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.ripple.RippleShader.RippleShape
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class WirelessChargingRippleControllerTest : SysuiTestCase() {
-
-    private val duration: Long = 1000L
-    private val fakeSystemClock: FakeSystemClock = FakeSystemClock()
-    private lateinit var wirelessChargingRippleController: WirelessChargingRippleController
-    private val fakeExecutor = FakeExecutor(fakeSystemClock)
-
-    @Before
-    fun setUp() {
-        wirelessChargingRippleController = WirelessChargingRippleController(
-                context, UiEventLoggerFake(), fakeExecutor, logcatLogBuffer())
-        fakeSystemClock.setElapsedRealtime(0L)
-    }
-
-    @Test
-    fun showWirelessChargingView_hasCorrectDuration() {
-        val wirelessChargingView = WirelessChargingView.create(
-                context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false,
-                RippleShape.ROUNDED_BOX, duration
-        )
-
-        wirelessChargingRippleController.show(wirelessChargingView, 0)
-        fakeExecutor.runAllReady()
-
-        assertEquals(duration, wirelessChargingView.duration)
-    }
-
-    @Test
-    fun showWirelessChargingView_triggerWhilePlayingAnim_doesNotShowRipple() {
-        val wirelessChargingViewFirst = WirelessChargingView.create(
-                context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false,
-                RippleShape.ROUNDED_BOX, duration
-        )
-        wirelessChargingRippleController.show(wirelessChargingViewFirst, 0)
-        assertThat(fakeExecutor.numPending()).isEqualTo(2)
-        fakeExecutor.runNextReady() // run showInternal
-
-        // ensure we haven't run hideInternal, to simulate the first one is still playing.
-        assertThat(fakeExecutor.numPending()).isEqualTo(1)
-
-        val wirelessChargingViewSecond = WirelessChargingView.create(
-                context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false,
-                RippleShape.ROUNDED_BOX, duration
-        )
-        wirelessChargingRippleController.show(wirelessChargingViewSecond, 0)
-
-        assertEquals(wirelessChargingViewFirst,
-                wirelessChargingRippleController.wirelessChargingView)
-    }
-
-    @Test
-    fun hideWirelessChargingView_afterDuration() {
-        fakeSystemClock.setElapsedRealtime(0L)
-        val wirelessChargingView = WirelessChargingView.create(
-                context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false,
-                RippleShape.ROUNDED_BOX, duration
-        )
-        wirelessChargingRippleController.show(wirelessChargingView, 0)
-
-        assertThat(fakeExecutor.numPending()).isEqualTo(2)
-        with(fakeExecutor) {
-            runNextReady() // run showInternal
-            advanceClockToNext()
-            runNextReady() // run hideInternal
-        }
-
-        assertEquals(null, wirelessChargingRippleController.wirelessChargingView)
-    }
-
-    @Test
-    fun showWirelessChargingView_withCallback_triggersCallback() {
-        val callback = object : WirelessChargingRippleController.Callback {
-            var onAnimationStartingCalled = false
-            var onAnimationEndedCalled = false
-
-            override fun onAnimationStarting() {
-                onAnimationStartingCalled = true
-            }
-
-            override fun onAnimationEnded() {
-                onAnimationEndedCalled = true
-            }
-        }
-        val wirelessChargingView = WirelessChargingView.create(
-                context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false,
-                RippleShape.CIRCLE, duration
-        )
-        wirelessChargingRippleController.show(wirelessChargingView, 0, callback)
-        assertThat(fakeExecutor.numPending()).isEqualTo(2)
-
-        fakeExecutor.runNextReady() // run showInternal
-        assertEquals(true, callback.onAnimationStartingCalled)
-
-        fakeExecutor.advanceClockToNext()
-
-        fakeExecutor.runNextReady() // run hideInternal
-        assertEquals(true, callback.onAnimationEndedCalled)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 7d54758..fa8f88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -43,7 +43,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
+import java.util.Collections;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -61,9 +61,6 @@
     private SmartSpaceComplication mComplication;
 
     @Mock
-    private ComplicationViewModel mComplicationViewModel;
-
-    @Mock
     private View mBcSmartspaceView;
 
     @Before
@@ -125,12 +122,12 @@
 
         // Test
         final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
-        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.singletonList(target));
         verify(mDreamOverlayStateController).addComplication(eq(mComplication));
     }
 
     @Test
-    public void testOverlayActive_targetsEmpty_removesComplication() {
+    public void testOverlayActive_targetsEmpty_addsComplication() {
         final SmartSpaceComplication.Registrant registrant = getRegistrant();
         registrant.start();
 
@@ -145,13 +142,9 @@
                 ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
         verify(mSmartspaceController).addListener(listenerCaptor.capture());
 
-        final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
-        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
-        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
-
         // Test
-        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList());
-        verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.emptyList());
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
     }
 
     @Test
@@ -170,8 +163,7 @@
                 ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
         verify(mSmartspaceController).addListener(listenerCaptor.capture());
 
-        final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
-        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+        listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.emptyList());
         verify(mDreamOverlayStateController).addComplication(eq(mComplication));
 
         // Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index ff579a1..318f2bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -41,7 +41,7 @@
      * specified. If not, an exception is thrown.
      */
     @Test
-    fun throwsIfUnspecifiedFlagIsAccessed() {
+    fun accessingUnspecifiedFlags_throwsException() {
         val flags: FeatureFlags = FakeFeatureFlags()
         try {
             assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
@@ -88,7 +88,7 @@
     }
 
     @Test
-    fun specifiedFlagsReturnCorrectValues() {
+    fun specifiedFlags_returnCorrectValues() {
         val flags = FakeFeatureFlags()
         flags.set(unreleasedFlag, false)
         flags.set(releasedFlag, false)
@@ -114,4 +114,125 @@
         assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
         assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
     }
+
+    @Test
+    fun listenerForBooleanFlag_calledOnlyWhenFlagChanged() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+        flags.addListener(unreleasedFlag, listener)
+
+        flags.set(unreleasedFlag, true)
+        flags.set(unreleasedFlag, true)
+        flags.set(unreleasedFlag, false)
+        flags.set(unreleasedFlag, false)
+
+        listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id)
+    }
+
+    @Test
+    fun listenerForStringFlag_calledOnlyWhenFlagChanged() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+        flags.addListener(stringFlag, listener)
+
+        flags.set(stringFlag, "Test")
+        flags.set(stringFlag, "Test")
+
+        listener.verifyInOrder(stringFlag.id)
+    }
+
+    @Test
+    fun listenerForBooleanFlag_notCalledAfterRemoved() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+        flags.addListener(unreleasedFlag, listener)
+        flags.set(unreleasedFlag, true)
+        flags.removeListener(listener)
+        flags.set(unreleasedFlag, false)
+
+        listener.verifyInOrder(unreleasedFlag.id)
+    }
+
+    @Test
+    fun listenerForStringFlag_notCalledAfterRemoved() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+
+        flags.addListener(stringFlag, listener)
+        flags.set(stringFlag, "Test")
+        flags.removeListener(listener)
+        flags.set(stringFlag, "Other")
+
+        listener.verifyInOrder(stringFlag.id)
+    }
+
+    @Test
+    fun listenerForMultipleFlags_calledWhenFlagsChange() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+        flags.addListener(unreleasedFlag, listener)
+        flags.addListener(releasedFlag, listener)
+
+        flags.set(releasedFlag, true)
+        flags.set(unreleasedFlag, true)
+
+        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+    }
+
+    @Test
+    fun listenerForMultipleFlags_notCalledAfterRemoved() {
+        val flags = FakeFeatureFlags()
+        val listener = VerifyingListener()
+
+        flags.addListener(unreleasedFlag, listener)
+        flags.addListener(releasedFlag, listener)
+        flags.set(releasedFlag, true)
+        flags.set(unreleasedFlag, true)
+        flags.removeListener(listener)
+        flags.set(releasedFlag, false)
+        flags.set(unreleasedFlag, false)
+
+        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+    }
+
+    @Test
+    fun multipleListenersForSingleFlag_allAreCalledWhenChanged() {
+        val flags = FakeFeatureFlags()
+        val listener1 = VerifyingListener()
+        val listener2 = VerifyingListener()
+        flags.addListener(releasedFlag, listener1)
+        flags.addListener(releasedFlag, listener2)
+
+        flags.set(releasedFlag, true)
+
+        listener1.verifyInOrder(releasedFlag.id)
+        listener2.verifyInOrder(releasedFlag.id)
+    }
+
+    @Test
+    fun multipleListenersForSingleFlag_removedListenerNotCalledAfterRemoval() {
+        val flags = FakeFeatureFlags()
+        val listener1 = VerifyingListener()
+        val listener2 = VerifyingListener()
+        flags.addListener(releasedFlag, listener1)
+        flags.addListener(releasedFlag, listener2)
+
+        flags.set(releasedFlag, true)
+        flags.removeListener(listener2)
+        flags.set(releasedFlag, false)
+
+        listener1.verifyInOrder(releasedFlag.id, releasedFlag.id)
+        listener2.verifyInOrder(releasedFlag.id)
+    }
+
+    class VerifyingListener : FlagListenable.Listener {
+        var flagEventIds = mutableListOf<Int>()
+        override fun onFlagChanged(event: FlagListenable.FlagEvent) {
+            flagEventIds.add(event.flagId)
+        }
+
+        fun verifyInOrder(vararg eventIds: Int) {
+            assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList())
+        }
+    }
 }
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 6e89bb9..21c018a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -48,7 +48,6 @@
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -107,7 +106,6 @@
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
-    private @Mock KeyguardViewMediatorLogger mLogger;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -264,8 +262,7 @@
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
                 mNotificationShadeWindowControllerLazy,
-                () -> mActivityLaunchAnimator,
-                mLogger);
+                () -> mActivityLaunchAnimator);
         mViewMediator.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 7b12eb7..56aff3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -41,18 +41,6 @@
     }
 
     @Test
-    fun log_shouldSaveLogToBufferWithException() {
-        val exception = createTestException("Some exception test message", "SomeExceptionTestClass")
-        buffer.log("Test", LogLevel.INFO, "Some test message", exception)
-
-        val dumpedString = dumpBuffer()
-
-        assertThat(dumpedString).contains("Some test message")
-        assertThat(dumpedString).contains("Some exception test message")
-        assertThat(dumpedString).contains("SomeExceptionTestClass")
-    }
-
-    @Test
     fun log_shouldRotateIfLogBufferIsFull() {
         buffer.log("Test", LogLevel.INFO, "This should be rotated")
         buffer.log("Test", LogLevel.INFO, "New test message")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 1785022..bef4695 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -1051,6 +1051,17 @@
     }
 
     @Test
+    fun bindDeviceWithNullName() {
+        val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
+        player.attachPlayer(viewHolder)
+        val state = mediaData.copy(device = device.copy(name = null))
+        player.bindPlayer(state, PACKAGE)
+        assertThat(seamless.isEnabled()).isTrue()
+        assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+        assertThat(seamless.contentDescription).isEqualTo(fallbackString)
+    }
+
+    @Test
     fun bindDeviceResumptionPlayer() {
         player.attachPlayer(viewHolder)
         val state = mediaData.copy(resumption = true)
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 d1ed8e9..f9c7d2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
@@ -108,6 +107,7 @@
     private val clock = FakeSystemClock()
     @Mock private lateinit var tunerService: TunerService
     @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
+    @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
@@ -974,7 +974,6 @@
     fun testPlaybackStateChange_keyExists_callsListener() {
         // Notification has been added
         addNotificationAndLoad()
-        val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // Callback gets an updated state
@@ -992,7 +991,6 @@
     @Test
     fun testPlaybackStateChange_keyDoesNotExist_doesNothing() {
         val state = PlaybackState.Builder().build()
-        val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // No media added with this key
@@ -1013,7 +1011,6 @@
 
         // And then get a state update
         val state = PlaybackState.Builder().build()
-        val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
         verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // Then no changes are made
@@ -1022,6 +1019,83 @@
             anyBoolean())
     }
 
+    @Test
+    fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        val state = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+                .build()
+        whenever(controller.playbackState).thenReturn(state)
+
+        addNotificationAndLoad()
+        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+        callbackCaptor.value.invoke(KEY, state)
+
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
+                capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+        assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+    }
+
+    @Test
+    fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
+        val desc = MediaDescription.Builder().run {
+            setTitle(SESSION_TITLE)
+            build()
+        }
+        val state = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+                .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+                .build()
+
+        // Add resumption controls in order to have semantic actions.
+        // To make sure that they are not null after changing state.
+        mediaDataManager.addResumptionControls(
+                USER_ID,
+                desc,
+                Runnable {},
+                session.sessionToken,
+                APP_NAME,
+                pendingIntent,
+                PACKAGE_NAME
+        )
+        backgroundExecutor.runAllReady()
+        foregroundExecutor.runAllReady()
+
+        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+        callbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+        verify(listener)
+                .onMediaDataLoaded(
+                        eq(PACKAGE_NAME),
+                        eq(PACKAGE_NAME),
+                        capture(mediaDataCaptor),
+                        eq(true),
+                        eq(0),
+                        eq(false)
+                )
+        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+        assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+    }
+
+    @Test
+    fun testPlaybackStateNull_Pause_keyExists_callsListener() {
+        whenever(controller.playbackState).thenReturn(null)
+        val state = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+                .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+                .build()
+
+        addNotificationAndLoad()
+        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+        callbackCaptor.value.invoke(KEY, state)
+
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
+                capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+        assertThat(mediaDataCaptor.value.semanticActions).isNull()
+    }
+
     /**
      * Helper function to add a media notification and capture the resulting MediaData
      */
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 ee10426..121c894 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -59,8 +59,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "TEST_KEY"
 private const val KEY_OLD = "TEST_KEY_OLD"
@@ -402,9 +402,10 @@
         manager.onMediaDataLoaded(KEY, null, mediaData)
         fakeBgExecutor.runAllReady()
         fakeFgExecutor.runAllReady()
-        // THEN the device is disabled
+        // THEN the device is disabled and name is set to null
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
     }
 
     @Test
@@ -421,9 +422,10 @@
         deviceCallback.onSelectedDeviceStateChanged(device, 1)
         fakeBgExecutor.runAllReady()
         fakeFgExecutor.runAllReady()
-        // THEN the device is disabled
+        // THEN the device is disabled and name is set to null
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
     }
 
     @Test
@@ -440,9 +442,24 @@
         deviceCallback.onDeviceListUpdate(mutableListOf(device))
         fakeBgExecutor.runAllReady()
         fakeFgExecutor.runAllReady()
-        // THEN the device is disabled
+        // THEN the device is disabled and name is set to null
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
+    }
+
+    @Test
+    fun mr2ReturnsRouteWithNullName_useLocalDeviceName() {
+        // GIVEN that MR2Manager returns a routing session that does not have a name
+        whenever(route.name).thenReturn(null)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is enabled and uses the current connected device name
+        val data = captureDeviceData(KEY)
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+        assertThat(data.enabled).isTrue()
     }
 
     @Test
@@ -647,12 +664,14 @@
             override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
             override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
             override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
-            override fun onBroadcastMetadataChanged(broadcastId: Int,
-                                                    metadata: BluetoothLeBroadcastMetadata) {}
+            override fun onBroadcastMetadataChanged(
+                broadcastId: Int,
+                metadata: BluetoothLeBroadcastMetadata
+            ) {}
         }
 
         bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
-        return callback;
+        return callback
     }
 
     fun setupLeAudioConfiguration(isLeAudio: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 1061e3c..fa47a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -35,7 +35,9 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -48,11 +50,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -78,6 +81,10 @@
     private lateinit var viewUtil: ViewUtil
     @Mock
     private lateinit var commandQueue: CommandQueue
+    @Mock
+    private lateinit var falsingManager: FalsingManager
+    @Mock
+    private lateinit var falsingCollector: FalsingCollector
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var fakeClock: FakeSystemClock
@@ -115,7 +122,9 @@
             accessibilityManager,
             configurationController,
             powerManager,
-            senderUiEventLogger
+            senderUiEventLogger,
+            falsingManager,
+            falsingCollector
         )
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -421,6 +430,38 @@
     }
 
     @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isFalse()
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isTrue()
+    }
+
+    @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 48fbd35..073c23c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,6 +23,7 @@
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
 import android.os.Bundle
+import android.os.UserHandle
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
@@ -97,7 +98,7 @@
         policy.setManagedProfile(USER_ID, false)
         policy.setDisplayContentInfo(
             policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
 
         val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
@@ -120,7 +121,7 @@
         // Indicate that the primary content belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
         policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
 
         val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
@@ -160,7 +161,7 @@
 
         policy.setManagedProfile(USER_ID, false)
         policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
 
         val processedRequest = processor.process(request)
 
@@ -183,7 +184,7 @@
         // Indicate that the primary content belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
         policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
 
         val processedRequest = processor.process(request)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
new file mode 100644
index 0000000..17396b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.IActivityTaskManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.Rect
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// The following values are chosen to be distinct from commonly seen real values
+private const val DISPLAY_ID = 100
+private const val PRIMARY_USER = 2000
+private const val MANAGED_PROFILE_USER = 3000
+
+@RunWith(AndroidTestingRunner::class)
+class ScreenshotPolicyImplTest : SysuiTestCase() {
+
+    @Test
+    fun testToDisplayContentInfo() {
+        assertThat(fullScreenWorkProfileTask.toDisplayContentInfo())
+            .isEqualTo(
+                DisplayContentInfo(
+                    ComponentName(
+                        "com.google.android.apps.nbu.files",
+                        "com.google.android.apps.nbu.files.home.HomeActivity"
+                    ),
+                    Rect(0, 0, 1080, 2400),
+                    UserHandle.of(MANAGED_PROFILE_USER),
+                    65))
+    }
+
+    @Test
+    fun findPrimaryContent_ignoresPipTask() = runBlocking {
+        val policy = fakeTasksPolicyImpl(
+            mContext,
+            shadeExpanded = false,
+            tasks = listOf(
+                    pipTask,
+                    fullScreenWorkProfileTask,
+                    launcherTask,
+                    emptyTask)
+        )
+
+        val info = policy.findPrimaryContent(DISPLAY_ID)
+        assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
+    }
+
+    @Test
+    fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
+        val policy = fakeTasksPolicyImpl(
+            mContext,
+            shadeExpanded = true,
+            tasks = listOf(
+                fullScreenWorkProfileTask,
+                launcherTask,
+                emptyTask)
+        )
+
+        val info = policy.findPrimaryContent(DISPLAY_ID)
+        assertThat(info).isEqualTo(policy.systemUiContent)
+    }
+
+    @Test
+    fun findPrimaryContent_emptyTaskList() = runBlocking {
+        val policy = fakeTasksPolicyImpl(
+            mContext,
+            shadeExpanded = false,
+            tasks = listOf()
+        )
+
+        val info = policy.findPrimaryContent(DISPLAY_ID)
+        assertThat(info).isEqualTo(policy.systemUiContent)
+    }
+
+    @Test
+    fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
+        val policy = fakeTasksPolicyImpl(
+            mContext,
+            shadeExpanded = false,
+            tasks = listOf(
+                launcherTask,
+                fullScreenWorkProfileTask,
+                emptyTask)
+        )
+
+        val info = policy.findPrimaryContent(DISPLAY_ID)
+        assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
+    }
+
+    private fun fakeTasksPolicyImpl(
+        context: Context,
+        shadeExpanded: Boolean,
+        tasks: List<RootTaskInfo>
+    ): ScreenshotPolicyImpl {
+        val userManager = mock<UserManager>()
+        val atmService = mock<IActivityTaskManager>()
+        val dispatcher = Dispatchers.Unconfined
+
+        return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher) {
+            override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
+            override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
+            override suspend fun isNotificationShadeExpanded() = shadeExpanded
+        }
+    }
+
+    private val pipTask = RootTaskInfo().apply {
+        configuration.windowConfiguration.apply {
+            windowingMode = WINDOWING_MODE_PINNED
+            bounds = Rect(628, 1885, 1038, 2295)
+            activityType = ACTIVITY_TYPE_STANDARD
+        }
+        displayId = DISPLAY_ID
+        userId = PRIMARY_USER
+        taskId = 66
+        visible = true
+        isVisible = true
+        isRunning = true
+        numActivities = 1
+        topActivity = ComponentName(
+            "com.google.android.youtube",
+            "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
+        )
+        childTaskIds = intArrayOf(66)
+        childTaskNames = arrayOf("com.google.android.youtube/" +
+                "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity")
+        childTaskUserIds = intArrayOf(0)
+        childTaskBounds = arrayOf(Rect(628, 1885, 1038, 2295))
+    }
+
+    private val fullScreenWorkProfileTask = RootTaskInfo().apply {
+        configuration.windowConfiguration.apply {
+            windowingMode = WINDOWING_MODE_FULLSCREEN
+            bounds = Rect(0, 0, 1080, 2400)
+            activityType = ACTIVITY_TYPE_STANDARD
+        }
+        displayId = DISPLAY_ID
+        userId = MANAGED_PROFILE_USER
+        taskId = 65
+        visible = true
+        isVisible = true
+        isRunning = true
+        numActivities = 1
+        topActivity = ComponentName(
+            "com.google.android.apps.nbu.files",
+            "com.google.android.apps.nbu.files.home.HomeActivity"
+        )
+        childTaskIds = intArrayOf(65)
+        childTaskNames = arrayOf("com.google.android.apps.nbu.files/" +
+                "com.google.android.apps.nbu.files.home.HomeActivity")
+        childTaskUserIds = intArrayOf(MANAGED_PROFILE_USER)
+        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
+    }
+
+    private val launcherTask = RootTaskInfo().apply {
+        configuration.windowConfiguration.apply {
+            windowingMode = WINDOWING_MODE_FULLSCREEN
+            bounds = Rect(0, 0, 1080, 2400)
+            activityType = ACTIVITY_TYPE_HOME
+        }
+        displayId = DISPLAY_ID
+        taskId = 1
+        userId = PRIMARY_USER
+        visible = true
+        isVisible = true
+        isRunning = true
+        numActivities = 1
+        topActivity = ComponentName(
+            "com.google.android.apps.nexuslauncher",
+            "com.google.android.apps.nexuslauncher.NexusLauncherActivity",
+        )
+        childTaskIds = intArrayOf(1)
+        childTaskNames = arrayOf("com.google.android.apps.nexuslauncher/" +
+                "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+        childTaskUserIds = intArrayOf(0)
+        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
+    }
+
+    private val emptyTask = RootTaskInfo().apply {
+        configuration.windowConfiguration.apply {
+            windowingMode = WINDOWING_MODE_FULLSCREEN
+            bounds = Rect(0, 0, 1080, 2400)
+            activityType = ACTIVITY_TYPE_UNDEFINED
+        }
+        displayId = DISPLAY_ID
+        taskId = 2
+        userId = PRIMARY_USER
+        visible = false
+        isVisible = false
+        isRunning = false
+        numActivities = 0
+        childTaskIds = intArrayOf(3, 4)
+        childTaskNames = arrayOf("", "")
+        childTaskUserIds = intArrayOf(0, 0)
+        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400), Rect(0, 2400, 1080, 4800))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 98389c2..e2ce939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -458,7 +458,6 @@
 
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
-                        mDumpManager,
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
                                 mInteractionJankMonitor),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 22d61a7..351dd0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -31,6 +32,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -178,9 +180,12 @@
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
     @Captor
     private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
+    @Captor
+    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
     private KeyguardStateController.Callback mKeyguardStateControllerCallback;
     private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
     private StatusBarStateController.StateListener mStatusBarStateListener;
+    private ScreenLifecycle.Observer mScreenObserver;
     private BroadcastReceiver mBroadcastReceiver;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private TestableLooper mTestableLooper;
@@ -273,6 +278,9 @@
                 mKeyguardUpdateMonitorCallbackCaptor.capture());
         mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
 
+        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
+        mScreenObserver = mScreenObserverCaptor.getValue();
+
         mExecutor.runAllReady();
         reset(mRotateTextViewController);
     }
@@ -537,6 +545,31 @@
     }
 
     @Test
+    public void transientIndication_visibleWhenWokenUp() {
+        createController();
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        final String message = "helpMsg";
+
+        // GIVEN screen is off
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_OFF);
+
+        // WHEN fingeprint help message received
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                message, BiometricSourceType.FINGERPRINT);
+
+        // THEN message isn't shown right away
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+        // WHEN the screen turns on
+        mScreenObserver.onScreenTurnedOn();
+        mTestableLooper.processAllMessages();
+
+        // THEN the message is shown
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
+    }
+
+    @Test
     public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
         createController();
         String message = mContext.getString(R.string.keyguard_unlock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index dd2b667..3cc4ee1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,15 +16,8 @@
 
 package com.android.systemui.statusbar;
 
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.content.Intent.ACTION_USER_SWITCHED;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -60,7 +53,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -359,93 +351,6 @@
         verify(listener, never()).onNotificationStateChanged();
     }
 
-    @Test
-    public void testShowSilentNotifications_settingSaysShow() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-        entry.setBucket(BUCKET_SILENT);
-
-        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-    }
-
-    @Test
-    public void testShowSilentNotifications_settingSaysHide() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_LOW)
-                .setNotification(notification)
-                .build();
-        entry.setBucket(BUCKET_SILENT);
-        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-    }
-
-    @Test
-    public void testShowSilentNotificationsPeopleBucket_settingSaysHide() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_LOW)
-                .setNotification(notification)
-                .build();
-        entry.setBucket(BUCKET_PEOPLE);
-        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-    }
-
-    @Test
-    public void testShowSilentNotificationsMediaBucket_settingSaysHide() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_LOW)
-                .setNotification(notification)
-                .build();
-        entry.setBucket(BUCKET_MEDIA_CONTROLS);
-        // always show media controls, even if they're silent
-        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-    }
-
-    @Test
-    public void testKeyguardNotificationSuppressors() {
-        // GIVEN a notification that should be shown on the lockscreen
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-        entry.setBucket(BUCKET_ALERTING);
-
-        // WHEN a suppressor is added that filters out all entries
-        FakeKeyguardSuppressor suppressor = new FakeKeyguardSuppressor();
-        mLockscreenUserManager.addKeyguardNotificationSuppressor(suppressor);
-
-        // THEN it's filtered out
-        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-
-        // WHEN the suppressor no longer filters out entries
-        suppressor.setShouldSuppress(false);
-
-        // THEN it's no longer filtered out
-        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry));
-    }
-
     private class TestNotificationLockscreenUserManager
             extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
@@ -478,17 +383,4 @@
             return mSettingsObserver;
         }
     }
-
-    private static class FakeKeyguardSuppressor implements KeyguardNotificationSuppressor {
-        private boolean mShouldSuppress = true;
-
-        @Override
-        public boolean shouldSuppressOnKeyguard(NotificationEntry entry) {
-            return mShouldSuppress;
-        }
-
-        public void setShouldSuppress(boolean shouldSuppress) {
-            mShouldSuppress = shouldSuppress;
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index 3fc0c81..b719c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -128,8 +127,6 @@
     @Test
     public void testNotNotifiedWithoutNotifications() {
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mLockScreenUserManager.shouldHideNotifications(anyInt())).thenReturn(
-                true);
         mDynamicPrivacyController.onUnlockedChanged();
         verifyNoMoreInteractions(mListener);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
deleted file mode 100644
index 19dd027..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ /dev/null
@@ -1,123 +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.statusbar.notification;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-
-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 NotificationListControllerTest extends SysuiTestCase {
-    private NotificationListController mController;
-
-    @Mock private NotificationEntryManager mEntryManager;
-    @Mock private NotificationListContainer mListContainer;
-    @Mock private DeviceProvisionedController mDeviceProvisionedController;
-
-    @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
-    @Captor private ArgumentCaptor<DeviceProvisionedListener> mProvisionedCaptor;
-
-    private NotificationEntryListener mEntryListener;
-    private DeviceProvisionedListener mProvisionedListener;
-
-    private int mNextNotifId = 0;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
-
-        mController = new NotificationListController(
-                mEntryManager,
-                mListContainer,
-                mDeviceProvisionedController);
-        mController.bind();
-
-        // Capture callbacks passed to mocks
-        verify(mEntryManager).addNotificationEntryListener(mEntryListenerCaptor.capture());
-        mEntryListener = mEntryListenerCaptor.getValue();
-        verify(mDeviceProvisionedController).addCallback(mProvisionedCaptor.capture());
-        mProvisionedListener = mProvisionedCaptor.getValue();
-    }
-
-    @Test
-    public void testCleanUpViewStateOnEntryRemoved() {
-        final NotificationEntry entry = buildEntry();
-        mEntryListener.onEntryRemoved(
-                entry,
-                NotificationVisibility.obtain(entry.getKey(), 0, 0, true),
-                false,
-                UNDEFINED_DISMISS_REASON);
-        verify(mListContainer).cleanUpViewStateForEntry(entry);
-    }
-
-    @Test
-    public void testCallUpdateNotificationsOnDeviceProvisionedChange() {
-        mProvisionedListener.onDeviceProvisionedChanged();
-        verify(mEntryManager).updateNotifications(anyString());
-    }
-
-    private NotificationEntry buildEntry() {
-        mNextNotifId++;
-
-        Notification.Builder n = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        return new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_NAME)
-                .setOpPkg(TEST_PACKAGE_NAME)
-                .setId(mNextNotifId)
-                .setUid(TEST_UID)
-                .setNotification(n.build())
-                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
-                .build();
-    }
-
-    private static final String TEST_PACKAGE_NAME = "test";
-    private static final int TEST_UID = 0;
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 4df99be..851517e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1518,7 +1518,7 @@
         verify(mCollectionListener).onRankingApplied();
         verifyNoMoreInteractions(mCollectionListener);
         verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
-        verify(mLogger, never()).logRecoveredRankings(any());
+        verify(mLogger, never()).logRecoveredRankings(any(), anyInt());
         clearInvocations(mCollectionListener, mLogger);
 
         // WHEN a ranking update includes key1 again
@@ -1530,7 +1530,7 @@
         verify(mCollectionListener).onRankingApplied();
         verifyNoMoreInteractions(mCollectionListener);
         verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
-        verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+        verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0));
     }
 
     @Test
@@ -1556,7 +1556,7 @@
         verify(mCollectionListener).onRankingApplied();
         verifyNoMoreInteractions(mCollectionListener);
         verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
-        verify(mLogger, never()).logRecoveredRankings(any());
+        verify(mLogger, never()).logRecoveredRankings(any(), anyInt());
         clearInvocations(mCollectionListener, mLogger);
 
         // WHEN a ranking update includes key1 again
@@ -1568,7 +1568,7 @@
         verify(mCollectionListener).onRankingApplied();
         verifyNoMoreInteractions(mCollectionListener);
         verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
-        verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+        verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt
deleted file mode 100644
index c17fe6f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.collection.legacy
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.GroupEventDispatcher
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener
-import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
-import com.android.systemui.util.mockito.mock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class GroupEventDispatcherTest : SysuiTestCase() {
-    val groupMap = mutableMapOf<String, NotificationGroup>()
-    val groupTestHelper = NotificationGroupTestHelper(mContext)
-
-    private val dispatcher = GroupEventDispatcher(groupMap::get)
-    private val listener: OnGroupChangeListener = mock()
-
-    @Before
-    fun setup() {
-        dispatcher.registerGroupChangeListener(listener)
-    }
-
-    @Test
-    fun testOnGroupsChangedUnbuffered() {
-        dispatcher.notifyGroupsChanged()
-        verify(listener).onGroupsChanged()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testOnGroupsChangedBuffered() {
-        dispatcher.openBufferScope()
-        dispatcher.notifyGroupsChanged()
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupsChanged()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testOnGroupsChangedDoubleBuffered() {
-        dispatcher.openBufferScope()
-        dispatcher.notifyGroupsChanged()
-        dispatcher.openBufferScope() // open a nested buffer scope
-        dispatcher.notifyGroupsChanged()
-        dispatcher.closeBufferScope() // should NOT flush events
-        dispatcher.notifyGroupsChanged()
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope() // this SHOULD flush events
-        verify(listener).onGroupsChanged()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testOnGroupsChangedBufferCoalesces() {
-        dispatcher.openBufferScope()
-        dispatcher.notifyGroupsChanged()
-        dispatcher.notifyGroupsChanged()
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupsChanged()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testOnGroupCreatedIsNeverBuffered() {
-        val group = addGroup(1)
-
-        dispatcher.openBufferScope()
-        dispatcher.notifyGroupCreated(group)
-        verify(listener).onGroupCreated(group, group.groupKey)
-        verifyNoMoreInteractions(listener)
-
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testOnGroupRemovedIsNeverBuffered() {
-        val group = addGroup(1)
-
-        dispatcher.openBufferScope()
-        dispatcher.notifyGroupRemoved(group)
-        verify(listener).onGroupRemoved(group, group.groupKey)
-        verifyNoMoreInteractions(listener)
-
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideAddedUnbuffered() {
-        val group = addGroup(1)
-        val newAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = newAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, null)
-        verify(listener).onGroupAlertOverrideChanged(group, null, newAlertEntry)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideRemovedUnbuffered() {
-        val group = addGroup(1)
-        val oldAlertEntry = groupTestHelper.createChildNotification()
-        dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
-        verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, null)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideChangedUnbuffered() {
-        val group = addGroup(1)
-        val oldAlertEntry = groupTestHelper.createChildNotification()
-        val newAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = newAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
-        verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideChangedBuffered() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        val oldAlertEntry = groupTestHelper.createChildNotification()
-        val newAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = newAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideIgnoredIfRemoved() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        val oldAlertEntry = groupTestHelper.createChildNotification()
-        val newAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = newAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
-        verifyNoMoreInteractions(listener)
-        groupMap.clear()
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideMultipleChangesBuffered() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        val oldAlertEntry = groupTestHelper.createChildNotification()
-        val newAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = null
-        dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry)
-        group.alertOverride = newAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, null)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideTemporaryValueSwallowed() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        val stableAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = null
-        dispatcher.notifyAlertOverrideChanged(group, stableAlertEntry)
-        group.alertOverride = stableAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, null)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testAlertOverrideTemporaryNullSwallowed() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        val temporaryAlertEntry = groupTestHelper.createChildNotification()
-        group.alertOverride = temporaryAlertEntry
-        dispatcher.notifyAlertOverrideChanged(group, null)
-        group.alertOverride = null
-        dispatcher.notifyAlertOverrideChanged(group, temporaryAlertEntry)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOnUnbuffered() {
-        val group = addGroup(1)
-        group.suppressed = true
-        dispatcher.notifySuppressedChanged(group)
-        verify(listener).onGroupSuppressionChanged(group, true)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOffUnbuffered() {
-        val group = addGroup(1)
-        group.suppressed = false
-        dispatcher.notifySuppressedChanged(group)
-        verify(listener).onGroupSuppressionChanged(group, false)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOnBuffered() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        group.suppressed = false
-        dispatcher.notifySuppressedChanged(group)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupSuppressionChanged(group, false)
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOnIgnoredIfRemoved() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        group.suppressed = false
-        dispatcher.notifySuppressedChanged(group)
-        verifyNoMoreInteractions(listener)
-        groupMap.clear()
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOnOffBuffered() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        group.suppressed = true
-        dispatcher.notifySuppressedChanged(group)
-        group.suppressed = false
-        dispatcher.notifySuppressedChanged(group)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verifyNoMoreInteractions(listener)
-    }
-
-    @Test
-    fun testSuppressOnOffOnBuffered() {
-        dispatcher.openBufferScope()
-        val group = addGroup(1)
-        group.suppressed = true
-        dispatcher.notifySuppressedChanged(group)
-        group.suppressed = false
-        dispatcher.notifySuppressedChanged(group)
-        group.suppressed = true
-        dispatcher.notifySuppressedChanged(group)
-        verifyNoMoreInteractions(listener)
-        dispatcher.closeBufferScope()
-        verify(listener).onGroupSuppressionChanged(group, true)
-        verifyNoMoreInteractions(listener)
-    }
-
-    private fun addGroup(id: Int): NotificationGroup {
-        val group = NotificationGroup("group:$id")
-        groupMap[group.groupKey] = group
-        return group
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
new file mode 100644
index 0000000..6eb391a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() {
+    private val logger: NotifCollectionLogger = mock()
+    private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build()
+    private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build()
+    private val collectionSet = mutableSetOf<String>()
+    private val coalescedSet = mutableSetOf<String>()
+    private lateinit var inconsistencyTracker: NotifCollectionInconsistencyTracker
+
+    private fun mapOfEntries(vararg entries: NotificationEntry): Map<String, NotificationEntry> =
+        entries.associateBy { it.key }
+
+    private fun rankingMapOf(vararg entries: NotificationEntry): RankingMap =
+        RankingMap(entries.map { it.ranking }.toTypedArray())
+
+    @Before
+    fun setUp() {
+        inconsistencyTracker = NotifCollectionInconsistencyTracker(logger)
+        inconsistencyTracker.attach({ collectionSet }, { coalescedSet })
+        collectionSet.clear()
+        coalescedSet.clear()
+    }
+
+    @Test
+    fun maybeLogInconsistentRankings_logsNewlyInconsistentRanking() {
+        val rankingMap = rankingMapOf(entry1)
+        inconsistencyTracker.maybeLogInconsistentRankings(
+            oldKeysWithoutRankings = emptySet(),
+            newEntriesWithoutRankings = mapOfEntries(entry2),
+            rankingMap = rankingMap
+        )
+        verify(logger).logMissingRankings(
+            newlyInconsistentEntries = eq(listOf(entry2)),
+            totalInconsistent = eq(1),
+            rankingMap = eq(rankingMap),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogInconsistentRankings_doesNotLogAlreadyInconsistentRanking() {
+        inconsistencyTracker.maybeLogInconsistentRankings(
+            oldKeysWithoutRankings = setOf(entry2.key),
+            newEntriesWithoutRankings = mapOfEntries(entry2),
+            rankingMap = rankingMapOf(entry1)
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogInconsistentRankings_logsWhenRankingIsAdded() {
+        inconsistencyTracker.maybeLogInconsistentRankings(
+            oldKeysWithoutRankings = setOf(entry2.key),
+            newEntriesWithoutRankings = mapOfEntries(),
+            rankingMap = rankingMapOf(entry1, entry2)
+        )
+        verify(logger).logRecoveredRankings(
+            newlyConsistentKeys = eq(listOf(entry2.key)),
+            totalInconsistent = eq(0),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogInconsistentRankings_doesNotLogsWhenEntryIsRemoved() {
+        inconsistencyTracker.maybeLogInconsistentRankings(
+            oldKeysWithoutRankings = setOf(entry2.key),
+            newEntriesWithoutRankings = mapOfEntries(),
+            rankingMap = rankingMapOf(entry1)
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogMissingNotifications_logsNewlyMissingNotifications() {
+        inconsistencyTracker.maybeLogMissingNotifications(
+            oldMissingKeys = setOf("a"),
+            newMissingKeys = setOf("a", "b"),
+        )
+        verify(logger).logMissingNotifications(
+            newlyMissingKeys = eq(listOf("b")),
+            totalMissing = eq(2),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogMissingNotifications_logsNoLongerMissingNotifications() {
+        inconsistencyTracker.maybeLogMissingNotifications(
+            oldMissingKeys = setOf("a", "b"),
+            newMissingKeys = setOf("a"),
+        )
+        verify(logger).logFoundNotifications(
+            newlyFoundKeys = eq(listOf("b")),
+            totalMissing = eq(1),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogMissingNotifications_logsBothAtOnce() {
+        inconsistencyTracker.maybeLogMissingNotifications(
+            oldMissingKeys = setOf("a"),
+            newMissingKeys = setOf("b"),
+        )
+        verify(logger).logFoundNotifications(
+            newlyFoundKeys = eq(listOf("a")),
+            totalMissing = eq(1),
+        )
+        verify(logger).logMissingNotifications(
+            newlyMissingKeys = eq(listOf("b")),
+            totalMissing = eq(1),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun maybeLogMissingNotifications_logsNothingWhenNoChange() {
+        inconsistencyTracker.maybeLogMissingNotifications(
+            oldMissingKeys = setOf("a"),
+            newMissingKeys = setOf("a"),
+        )
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun logNewMissingNotifications_doesNotLogForConsistentSets() {
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf())
+        verifyNoMoreInteractions(logger)
+
+        collectionSet.add(entry1.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1))
+        verifyNoMoreInteractions(logger)
+
+        coalescedSet.add(entry2.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1, entry2))
+        verifyNoMoreInteractions(logger)
+
+        coalescedSet.add(entry1.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1, entry2))
+        verifyNoMoreInteractions(logger)
+
+        coalescedSet.remove(entry1.key)
+        collectionSet.remove(entry1.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry2))
+        verifyNoMoreInteractions(logger)
+
+        coalescedSet.remove(entry2.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf())
+        verifyNoMoreInteractions(logger)
+    }
+
+    @Test
+    fun logNewMissingNotifications_logsAsExpected() {
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf())
+        verifyNoMoreInteractions(logger)
+
+        collectionSet.add(entry1.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1, entry2))
+        verify(logger).logMissingNotifications(
+            newlyMissingKeys = eq(listOf(entry2.key)),
+            totalMissing = eq(1),
+        )
+        verifyNoMoreInteractions(logger)
+        clearInvocations(logger)
+
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1, entry2))
+        verifyNoMoreInteractions(logger)
+
+        coalescedSet.add(entry2.key)
+        inconsistencyTracker.logNewMissingNotifications(rankingMapOf(entry1, entry2))
+        verify(logger).logFoundNotifications(
+            newlyFoundKeys = eq(listOf(entry2.key)),
+            totalMissing = eq(0),
+        )
+        verifyNoMoreInteractions(logger)
+        clearInvocations(logger)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
deleted file mode 100644
index 6c07174..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.collection.notifcollection
-
-import android.service.notification.NotificationListenerService.RankingMap
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotifCollectionLoggerTest : SysuiTestCase() {
-    private val logger: NotifCollectionLogger = mock()
-    private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build()
-    private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build()
-
-    private fun mapOfEntries(vararg entries: NotificationEntry): Map<String, NotificationEntry> =
-        entries.associateBy { it.key }
-
-    private fun rankingMapOf(vararg entries: NotificationEntry): RankingMap =
-        RankingMap(entries.map { it.ranking }.toTypedArray())
-
-    @Test
-    fun testMaybeLogInconsistentRankings_logsNewlyInconsistentRanking() {
-        val rankingMap = rankingMapOf(entry1)
-        maybeLogInconsistentRankings(
-            logger = logger,
-            oldKeysWithoutRankings = emptySet(),
-            newEntriesWithoutRankings = mapOfEntries(entry2),
-            rankingMap = rankingMap
-        )
-        verify(logger).logMissingRankings(
-            newlyInconsistentEntries = eq(listOf(entry2)),
-            totalInconsistent = eq(1),
-            rankingMap = eq(rankingMap),
-        )
-        verifyNoMoreInteractions(logger)
-    }
-
-    @Test
-    fun testMaybeLogInconsistentRankings_doesNotLogAlreadyInconsistentRanking() {
-        maybeLogInconsistentRankings(
-            logger = logger,
-            oldKeysWithoutRankings = setOf(entry2.key),
-            newEntriesWithoutRankings = mapOfEntries(entry2),
-            rankingMap = rankingMapOf(entry1)
-        )
-        verifyNoMoreInteractions(logger)
-    }
-
-    @Test
-    fun testMaybeLogInconsistentRankings_logsWhenRankingIsAdded() {
-        maybeLogInconsistentRankings(
-            logger = logger,
-            oldKeysWithoutRankings = setOf(entry2.key),
-            newEntriesWithoutRankings = mapOfEntries(),
-            rankingMap = rankingMapOf(entry1, entry2)
-        )
-        verify(logger).logRecoveredRankings(
-            newlyConsistentKeys = eq(listOf(entry2.key)),
-        )
-        verifyNoMoreInteractions(logger)
-    }
-
-    @Test
-    fun testMaybeLogInconsistentRankings_doesNotLogsWhenEntryIsRemoved() {
-        maybeLogInconsistentRankings(
-            logger = logger,
-            oldKeysWithoutRankings = setOf(entry2.key),
-            newEntriesWithoutRankings = mapOfEntries(),
-            rankingMap = rankingMapOf(entry1)
-        )
-        verifyNoMoreInteractions(logger)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleFakes.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleFakes.kt
deleted file mode 100644
index 1fb4ca1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleFakes.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.people
-
-object EmptySubscription : Subscription {
-    override fun unsubscribe() {}
-}
-
-class FakeDataSource<T>(
-    private val data: T,
-    private val subscription: Subscription = EmptySubscription
-) : DataSource<T> {
-    override fun registerListener(listener: DataListener<T>): Subscription {
-        listener.onDataChanged(data)
-        return subscription
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
deleted file mode 100644
index 5898664..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ /dev/null
@@ -1,179 +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.statusbar.notification.people
-
-import android.graphics.drawable.Drawable
-import android.testing.AndroidTestingRunner
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import kotlin.reflect.KClass
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PeopleHubViewControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var mockViewBoundary: PeopleHubViewBoundary
-    @Mock private lateinit var mockActivityStarter: ActivityStarter
-
-    @Test
-    fun testBindViewModelToViewBoundary() {
-        val fakePerson1 = fakePersonViewModel("name")
-        val fakeViewModel = PeopleHubViewModel(sequenceOf(fakePerson1), true)
-
-        val mockFactory = mock(PeopleHubViewModelFactory::class.java)
-        whenever(mockFactory.createWithAssociatedClickView(any())).thenReturn(fakeViewModel)
-
-        val mockClickView = mock(View::class.java)
-        whenever(mockViewBoundary.associatedViewForClickAnimation).thenReturn(mockClickView)
-
-        val fakePersonViewAdapter1 = FakeDataListener<PersonViewModel?>()
-        val fakePersonViewAdapter2 = FakeDataListener<PersonViewModel?>()
-        whenever(mockViewBoundary.personViewAdapters)
-                .thenReturn(sequenceOf(fakePersonViewAdapter1, fakePersonViewAdapter2))
-
-        val adapter = PeopleHubViewAdapterImpl(FakeDataSource(mockFactory))
-
-        adapter.bindView(mockViewBoundary)
-
-        assertThat(fakePersonViewAdapter1.lastSeen).isEqualTo(Maybe.Just(fakePerson1))
-        assertThat(fakePersonViewAdapter2.lastSeen).isEqualTo(Maybe.Just<PersonViewModel?>(null))
-        verify(mockViewBoundary).setVisible(true)
-        verify(mockFactory).createWithAssociatedClickView(mockClickView)
-    }
-
-    @Test
-    fun testBindViewModelToViewBoundary_moreDataThanCanBeDisplayed_displaysMostRecent() {
-        val fakePerson1 = fakePersonViewModel("person1")
-        val fakePerson2 = fakePersonViewModel("person2")
-        val fakePerson3 = fakePersonViewModel("person3")
-        val fakePeople = sequenceOf(fakePerson3, fakePerson2, fakePerson1)
-        val fakeViewModel = PeopleHubViewModel(fakePeople, true)
-
-        val mockFactory = mock(PeopleHubViewModelFactory::class.java)
-        whenever(mockFactory.createWithAssociatedClickView(any())).thenReturn(fakeViewModel)
-
-        whenever(mockViewBoundary.associatedViewForClickAnimation)
-                .thenReturn(mock(View::class.java))
-
-        val fakePersonViewAdapter1 = FakeDataListener<PersonViewModel?>()
-        val fakePersonViewAdapter2 = FakeDataListener<PersonViewModel?>()
-        whenever(mockViewBoundary.personViewAdapters)
-                .thenReturn(sequenceOf(fakePersonViewAdapter1, fakePersonViewAdapter2))
-
-        val adapter = PeopleHubViewAdapterImpl(FakeDataSource(mockFactory))
-
-        adapter.bindView(mockViewBoundary)
-
-        assertThat(fakePersonViewAdapter1.lastSeen).isEqualTo(Maybe.Just(fakePerson3))
-        assertThat(fakePersonViewAdapter2.lastSeen).isEqualTo(Maybe.Just(fakePerson2))
-    }
-
-    @Test
-    fun testViewModelDataSourceTransformsModel() {
-        val fakeClickRunnable = mock(Runnable::class.java)
-        val fakePerson = fakePersonModel("id", "name", fakeClickRunnable)
-        val fakeModel = PeopleHubModel(listOf(fakePerson))
-        val fakeModelDataSource = FakeDataSource(fakeModel)
-        val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
-                mockActivityStarter,
-                fakeModelDataSource
-        )
-        val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
-        val mockClickView = mock(View::class.java)
-
-        factoryDataSource.registerListener(fakeListener)
-
-        val viewModel = (fakeListener.lastSeen as Maybe.Just).value
-                .createWithAssociatedClickView(mockClickView)
-        assertThat(viewModel.isVisible).isTrue()
-        val people = viewModel.people.toList()
-        assertThat(people.size).isEqualTo(1)
-        assertThat(people[0].name).isEqualTo("name")
-        assertThat(people[0].icon).isSameInstanceAs(fakePerson.avatar)
-
-        people[0].onClick()
-
-        verify(fakeClickRunnable).run()
-    }
-}
-
-/** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
-private inline fun <reified T : Any> any(): T {
-    return Mockito.any() ?: createInstance(T::class)
-}
-
-/** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
-private inline fun <reified T : Any> same(value: T): T {
-    return Mockito.same(value) ?: createInstance(T::class)
-}
-
-/** Creates an instance of the given class. */
-private fun <T : Any> createInstance(clazz: KClass<T>): T = castNull()
-
-/** Tricks the Kotlin compiler into assigning `null` to a non-nullable variable. */
-@Suppress("UNCHECKED_CAST")
-private fun <T> castNull(): T = null as T
-
-private fun fakePersonModel(
-    id: String,
-    name: CharSequence,
-    clickRunnable: Runnable,
-    userId: Int = 0
-): PersonModel =
-        PersonModel(id, userId, name, mock(Drawable::class.java), clickRunnable)
-
-private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
-        PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
-
-sealed class Maybe<T> {
-    data class Just<T>(val value: T) : Maybe<T>()
-    class Nothing<T> : Maybe<T>() {
-        override fun equals(other: Any?): Boolean {
-            if (this === other) return true
-            if (javaClass != other?.javaClass) return false
-            return true
-        }
-
-        override fun hashCode(): Int {
-            return javaClass.hashCode()
-        }
-    }
-}
-
-class FakeDataListener<T> : DataListener<T> {
-
-    var lastSeen: Maybe<T> = Maybe.Nothing()
-
-    override fun onDataChanged(data: T) {
-        lastSeen = Maybe.Just(data)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1460e04..966e233 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -34,7 +34,6 @@
 import android.content.res.Resources;
 import android.metrics.LogMaker;
 import android.testing.AndroidTestingRunner;
-import android.view.LayoutInflater;
 
 import androidx.test.filters.SmallTest;
 
@@ -42,11 +41,9 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -61,12 +58,9 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -112,7 +106,6 @@
     @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 DumpManager mDumpManager;
@@ -122,19 +115,14 @@
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private ScrimController mScrimController;
-    @Mock private NotificationGroupManagerLegacy mGroupManagerLegacy;
     @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private SectionHeaderController mSilentHeaderController;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NotifCollection mNotifCollection;
     @Mock private NotificationEntryManager mEntryManager;
-    @Mock private IStatusBarService mIStatusBarService;
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private LayoutInflater mLayoutInflater;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private StackStateLogger mStackLogger;
@@ -176,7 +164,6 @@
                 mNotificationSwipeHelperBuilder,
                 mCentralSurfaces,
                 mScrimController,
-                mGroupManagerLegacy,
                 mGroupExpansionManager,
                 mSilentHeaderController,
                 mNotifPipeline,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 8fd6842..35d2363b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -3,19 +3,21 @@
 import android.annotation.DimenRes
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.EmptyShadeView
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.mock
@@ -26,17 +28,20 @@
 
     private val hostView = FrameLayout(context)
     private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
-    private val expandableViewState = ExpandableViewState()
     private val notificationRow = mock(ExpandableNotificationRow::class.java)
     private val dumpManager = mock(DumpManager::class.java)
     private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
+    private val notificationShelf = mock(NotificationShelf::class.java)
+    private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
+        layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
+    }
 
     private val ambientState = AmbientState(
-        context,
-        dumpManager,
-        SectionProvider { _, _ -> false },
-        BypassController { false },
-        mStatusBarKeyguardViewManager
+            context,
+            dumpManager,
+            /* sectionProvider */ { _, _ -> false },
+            /* bypassController */ { false },
+            mStatusBarKeyguardViewManager
     )
 
     private val testableResources = mContext.orCreateTestableResources
@@ -49,7 +54,9 @@
 
     @Before
     fun setUp() {
-        whenever(notificationRow.viewState).thenReturn(expandableViewState)
+        whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
+        whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+
         hostView.addView(notificationRow)
     }
 
@@ -60,7 +67,8 @@
 
         stackScrollAlgorithm.resetViewStates(ambientState, 0)
 
-        assertThat(expandableViewState.yTranslation).isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+        assertThat(notificationRow.viewState.yTranslation)
+                .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
     }
 
     @Test
@@ -75,15 +83,12 @@
         stackScrollAlgorithm.resetViewStates(ambientState, 0)
 
         // top margin presence should decrease heads up translation up to minHeadsUpTranslation
-        assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation)
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(minHeadsUpTranslation)
     }
 
     @Test
     fun resetViewStates_emptyShadeView_isCenteredVertically() {
         stackScrollAlgorithm.initView(context)
-        val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
-            layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
-        }
         hostView.removeAllViews()
         hostView.addView(emptyShadeView)
         ambientState.layoutMaxHeight = 1280
@@ -98,6 +103,121 @@
     }
 
     @Test
+    fun resetViewStates_hunGoingToShade_viewBecomesOpaque() {
+        whenever(notificationRow.isAboveShelf).thenReturn(true)
+        ambientState.isShadeExpanded = true
+        ambientState.trackedHeadsUpRow = notificationRow
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(notificationRow.viewState.alpha).isEqualTo(1f)
+    }
+
+    @Test
+    fun resetViewStates_isExpansionChanging_viewBecomesTransparent() {
+        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+        ambientState.isExpansionChanging = true
+        ambientState.expansionFraction = 0.25f
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        val expected = getContentAlpha(0.25f)
+        assertThat(notificationRow.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
+    fun resetViewStates_isExpansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
+        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+        ambientState.isExpansionChanging = true
+        ambientState.expansionFraction = 0.25f
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        val expected = aboutToShowBouncerProgress(0.25f)
+        assertThat(notificationRow.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
+    fun resetViewStates_isOnKeyguard_viewBecomesTransparent() {
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.hideAmount = 0.25f
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(notificationRow.viewState.alpha).isEqualTo(1f - ambientState.hideAmount)
+    }
+
+    @Test
+    fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesTransparent() {
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.fractionToShade = 0.25f
+        stackScrollAlgorithm.initView(context)
+        hostView.removeAllViews()
+        hostView.addView(emptyShadeView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        val expected = getContentAlpha(ambientState.fractionToShade)
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
+    fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() {
+        ambientState.setStatusBarState(StatusBarState.SHADE)
+        ambientState.fractionToShade = 0.25f
+        stackScrollAlgorithm.initView(context)
+        hostView.removeAllViews()
+        hostView.addView(emptyShadeView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f)
+    }
+
+    @Test
+    fun resetViewStates_hiddenShelf_viewAlphaDoesNotChange() {
+        val expected = notificationShelf.viewState.alpha
+        notificationShelf.viewState.hidden = true
+        ambientState.shelf = notificationShelf
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(notificationShelf.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
+    fun resetViewStates_shelfTopLessThanViewTop_hidesView() {
+        notificationRow.viewState.yTranslation = 10f
+        notificationShelf.viewState.yTranslation = 0.9f
+        notificationShelf.viewState.hidden = false
+        ambientState.shelf = notificationShelf
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(notificationRow.viewState.alpha).isEqualTo(0f)
+    }
+
+    @Test
+    fun resetViewStates_shelfTopGreaterOrEqualThanViewTop_viewAlphaDoesNotChange() {
+        val expected = notificationRow.viewState.alpha
+        notificationRow.viewState.yTranslation = 10f
+        notificationShelf.viewState.yTranslation = 10f
+        notificationShelf.viewState.hidden = false
+        ambientState.shelf = notificationShelf
+        stackScrollAlgorithm.initView(context)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(notificationRow.viewState.alpha).isEqualTo(expected)
+    }
+
+    @Test
     fun getGapForLocation_onLockscreen_returnsSmallGap() {
         val gap = stackScrollAlgorithm.getGapForLocation(
                 /* fractionToShade= */ 0f, /* onKeyguard= */ true)
@@ -267,7 +387,6 @@
         assertEquals(10f, expandableViewState.yTranslation)
     }
 
-
     @Test
     fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() {
         val expandableViewState = ExpandableViewState()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index d2680eb5..cce2e89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -314,13 +316,6 @@
         mBiometricUnlockController.startWakeAndUnlock(
                 BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
 
-        // THEN we collpase the panels and notify authenticated
-        verify(mShadeController).animateCollapsePanels(
-                /* flags */ anyInt(),
-                /* force */ eq(true),
-                /* delayed */ eq(false),
-                /* speedUpFactor */ anyFloat()
-        );
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
                 /* strongAuth */ eq(false));
     }
@@ -395,15 +390,19 @@
     }
 
     @Test
-    public void onUdfpsConsecutivelyFailedTwoTimes_showBouncer() {
+    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
         // GIVEN UDFPS is supported
         when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
 
-        // WHEN udfps fails once - then don't show the bouncer
+        // WHEN udfps fails once - then don't show the bouncer yet
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
 
-        // WHEN udfps fails the second time
+        // WHEN udfps fails the second time - then don't show the bouncer yet
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+
+        // WHEN udpfs fails the third time
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN show the bouncer
@@ -427,8 +426,8 @@
     }
 
     @Test
-    public void onFPFailureNoHaptics_notDeviceInteractive_showBouncer() {
-        // GIVEN no vibrator and the screen is off
+    public void onFPFailureNoHaptics_notInteractive_showLockScreen() {
+        // GIVEN no vibrator and device is dreaming
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
         when(mUpdateMonitor.isDreaming()).thenReturn(false);
@@ -436,15 +435,12 @@
         // WHEN FP fails
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
-        // after device is finished waking up
-        mBiometricUnlockController.mWakefulnessObserver.onFinishedWakingUp();
-
-        // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        // THEN wakeup the device
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
     }
 
     @Test
-    public void onFPFailureNoHaptics_dreaming_showBouncer() {
+    public void onFPFailureNoHaptics_dreaming_showLockScreen() {
         // GIVEN no vibrator and device is dreaming
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -453,7 +449,7 @@
         // WHEN FP fails
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
-        // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        // THEN wakeup the device
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 3443ae6..735a88d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.charging.WiredChargingRippleController;
-import com.android.systemui.charging.WirelessChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -134,7 +133,6 @@
 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.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
@@ -210,7 +208,6 @@
     @Mock private DozeScrimController mDozeScrimController;
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private NotificationListener mNotificationListener;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@@ -284,7 +281,6 @@
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
-    @Mock private WirelessChargingRippleController mWirelessChargingRippleController;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -467,9 +463,7 @@
                 mActivityLaunchAnimator,
                 mJankMonitor,
                 mDeviceStateManager,
-                mWiredChargingRippleController,
-                mWirelessChargingRippleController,
-                mDreamManager);
+                mWiredChargingRippleController, mDreamManager);
         when(mKeyguardViewMediator.registerCentralSurfaces(
                 any(CentralSurfacesImpl.class),
                 any(NotificationPanelViewController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index a6b7e51..ca98143 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -58,7 +58,6 @@
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
-        mDependency.injectMockDependency(DarkIconDispatcher.class);
     }
 
     @Test
@@ -75,7 +74,8 @@
                 layout,
                 mock(FeatureFlags.class),
                 mock(StatusBarPipelineFlags.class),
-                () -> mock(WifiViewModel.class));
+                () -> mock(WifiViewModel.class),
+                mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
     }
 
@@ -116,8 +116,10 @@
                 LinearLayout group,
                 FeatureFlags featureFlags,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                Provider<WifiViewModel> wifiViewModelProvider) {
-            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
+                Provider<WifiViewModel> wifiViewModelProvider,
+                DarkIconDispatcher darkIconDispatcher) {
+            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider,
+                    darkIconDispatcher);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
new file mode 100644
index 0000000..3d29d2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import org.junit.Test
+
+@SmallTest
+class WifiNetworkModelTest : SysuiTestCase() {
+    @Test
+    fun active_levelsInValidRange_noException() {
+        (MIN_VALID_LEVEL..MAX_VALID_LEVEL).forEach { level ->
+            WifiNetworkModel.Active(NETWORK_ID, level = level)
+            // No assert, just need no crash
+        }
+    }
+
+    @Test
+    fun active_levelNull_noException() {
+        WifiNetworkModel.Active(NETWORK_ID, level = null)
+        // No assert, just need no crash
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun active_levelNegative_exceptionThrown() {
+        WifiNetworkModel.Active(NETWORK_ID, level = MIN_VALID_LEVEL - 1)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun active_levelTooHigh_exceptionThrown() {
+        WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
+    }
+
+    companion object {
+        private const val NETWORK_ID = 2
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index d0a3808..d070ba0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -19,6 +19,7 @@
 import android.net.ConnectivityManager
 import android.net.Network
 import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
@@ -39,11 +40,14 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -61,20 +65,28 @@
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     private lateinit var executor: Executor
+    private lateinit var scope: CoroutineScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         executor = FakeExecutor(FakeSystemClock())
+        scope = CoroutineScope(IMMEDIATE)
 
         underTest = WifiRepositoryImpl(
             connectivityManager,
-            wifiManager,
-            executor,
             logger,
+            executor,
+            scope,
+            wifiManager,
         )
     }
 
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
     @Test
     fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
@@ -115,6 +127,61 @@
     }
 
     @Test
+    fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(true)
+            whenever(this.isCarrierMerged).thenReturn(true)
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+        assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
+        )
+
+        assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_validated_networkValidated() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
+        )
+
+        assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
     fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
         val job = underTest
@@ -282,13 +349,14 @@
             .launchIn(this)
 
         val wifiInfo = mock<WifiInfo>().apply {
-        whenever(this.ssid).thenReturn(SSID)
-        whenever(this.isPrimary).thenReturn(true)
-    }
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(true)
+        }
 
         // Start with the original network
-        getNetworkCallback()
-            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(wifiInfo, isValidated = true)
+        )
 
         // WHEN we keep the same network ID but change the SSID
         val newSsid = "CD"
@@ -297,14 +365,16 @@
             whenever(this.isPrimary).thenReturn(true)
         }
 
-        getNetworkCallback()
-            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(newWifiInfo))
+        getNetworkCallback().onCapabilitiesChanged(
+            NETWORK, createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
+        )
 
         // THEN we've updated to the new SSID
         assertThat(latest is WifiNetworkModel.Active).isTrue()
         val latestActive = latest as WifiNetworkModel.Active
         assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
         assertThat(latestActive.ssid).isEqualTo(newSsid)
+        assertThat(latestActive.isValidated).isFalse()
 
         job.cancel()
     }
@@ -403,13 +473,48 @@
         job.cancel()
     }
 
+    /** Regression test for b/244173280. */
+    @Test
+    fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = runBlocking(IMMEDIATE) {
+        var latest1: WifiNetworkModel? = null
+        val job1 = underTest
+            .wifiNetwork
+            .onEach { latest1 = it }
+            .launchIn(this)
+
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+
+        assertThat(latest1 is WifiNetworkModel.Active).isTrue()
+        val latest1Active = latest1 as WifiNetworkModel.Active
+        assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latest1Active.ssid).isEqualTo(SSID)
+
+        // WHEN we add a second subscriber after having already emitted a value
+        var latest2: WifiNetworkModel? = null
+        val job2 = underTest
+            .wifiNetwork
+            .onEach { latest2 = it }
+            .launchIn(this)
+
+        // THEN the second subscribe receives the already-emitted value
+        assertThat(latest2 is WifiNetworkModel.Active).isTrue()
+        val latest2Active = latest2 as WifiNetworkModel.Active
+        assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latest2Active.ssid).isEqualTo(SSID)
+
+        job1.cancel()
+        job2.cancel()
+    }
+
     @Test
     fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
         underTest = WifiRepositoryImpl(
-                connectivityManager,
-                wifiManager = null,
-                executor,
-                logger,
+            connectivityManager,
+            logger,
+            executor,
+            scope,
+            wifiManager = null,
         )
 
         var latest: WifiActivityModel? = null
@@ -501,11 +606,16 @@
         return callbackCaptor.value!!
     }
 
-    private fun createWifiNetworkCapabilities(wifiInfo: WifiInfo) =
-        mock<NetworkCapabilities>().apply {
-            whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-            whenever(this.transportInfo).thenReturn(wifiInfo)
+    private fun createWifiNetworkCapabilities(
+        wifiInfo: WifiInfo,
+        isValidated: Boolean = true,
+    ): NetworkCapabilities {
+        return mock<NetworkCapabilities>().also {
+            whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+            whenever(it.transportInfo).thenReturn(wifiInfo)
+            whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
         }
+    }
 
     private companion object {
         const val NETWORK_ID = 45
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index 5f1b1db..9d8b4bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -126,6 +126,38 @@
     }
 
     @Test
+    fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.Inactive)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+        var latest: Boolean? = null
+        val job = underTest
+            .hasActivityIn
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+        var latest: Boolean? = null
+        val job = underTest
+            .hasActivityIn
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
     fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
         repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
 
@@ -159,6 +191,28 @@
         job.cancel()
     }
 
+    @Test
+    fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
+        val wifiNetwork = WifiNetworkModel.Active(
+            networkId = 45,
+            isValidated = true,
+            level = 3,
+            ssid = "AB",
+            passpointProviderFriendlyName = "friendly"
+        )
+        repository.setWifiNetwork(wifiNetwork)
+
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo(wifiNetwork)
+
+        job.cancel()
+    }
+
     companion object {
         val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index c790734..f0a775b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,6 +18,9 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
@@ -62,14 +65,103 @@
             logger,
             interactor
         )
-
-        // Set up with a valid SSID
-        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB"))
     }
 
     @Test
-    fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) {
+    fun wifiIconResId_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+        var latest: Int? = null
+        val job = underTest
+                .wifiIconResId
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(WIFI_NO_NETWORK)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIconResId_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+
+        var latest: Int? = null
+        val job = underTest
+                .wifiIconResId
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIconResId_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
+
+        var latest: Int? = null
+        val job = underTest
+            .wifiIconResId
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIconResId_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
+        val level = 1
+
+        repository.setWifiNetwork(
+                WifiNetworkModel.Active(
+                        NETWORK_ID,
+                        isValidated = true,
+                        level = level
+                )
+        )
+
+        var latest: Int? = null
+        val job = underTest
+                .wifiIconResId
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(WIFI_FULL_ICONS[level])
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIconResId_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
+        val level = 4
+
+        repository.setWifiNetwork(
+                WifiNetworkModel.Active(
+                        NETWORK_ID,
+                        isValidated = false,
+                        level = level
+                )
+        )
+
+        var latest: Int? = null
+        val job = underTest
+                .wifiIconResId
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(false)
+        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -86,6 +178,7 @@
     @Test
     fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(false)
+        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -104,8 +197,9 @@
     }
 
     @Test
-    fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) {
+    fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(true)
+        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -122,6 +216,11 @@
 
         job.cancel()
     }
+
+    companion object {
+        private const val NETWORK_ID = 2
+        private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(NETWORK_ID, ssid = "AB")
+    }
 }
 
 private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
new file mode 100644
index 0000000..092e82c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.kotlin
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PairwiseFlowTest : SysuiTestCase() {
+    @Test
+    fun simple() = runBlocking {
+        assertThatFlow((1..3).asFlow().pairwise())
+            .emitsExactly(
+                WithPrev(1, 2),
+                WithPrev(2, 3),
+            )
+    }
+
+    @Test
+    fun notEnough() = runBlocking {
+        assertThatFlow(flowOf(1).pairwise()).emitsNothing()
+    }
+
+    @Test
+    fun withInit() = runBlocking {
+        assertThatFlow(flowOf(2).pairwise(initialValue = 1))
+            .emitsExactly(WithPrev(1, 2))
+    }
+
+    @Test
+    fun notEnoughWithInit() = runBlocking {
+        assertThatFlow(emptyFlow<Int>().pairwise(initialValue = 1)).emitsNothing()
+    }
+
+    @Test
+    fun withStateFlow() = runBlocking(Dispatchers.Main.immediate) {
+        val state = MutableStateFlow(1)
+        val stop = MutableSharedFlow<Unit>()
+
+        val stoppable = merge(state, stop)
+            .takeWhile { it is Int }
+            .filterIsInstance<Int>()
+
+        val job1 = launch {
+            assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2))
+        }
+        state.value = 2
+        val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
+
+        stop.emit(Unit)
+
+        assertThatJob(job1).isCompleted()
+        assertThatJob(job2).isCompleted()
+    }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SetChangesFlowTest : SysuiTestCase() {
+    @Test
+    fun simple() = runBlocking {
+        assertThatFlow(
+            flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()
+        ).emitsExactly(
+            SetChanges(
+                added = setOf(1, 2, 3),
+                removed = emptySet(),
+            ),
+            SetChanges(
+                added = setOf(4),
+                removed = setOf(1),
+            ),
+        )
+    }
+
+    @Test
+    fun onlyOneEmission() = runBlocking {
+        assertThatFlow(flowOf(setOf(1)).setChanges())
+            .emitsExactly(
+                SetChanges(
+                    added = setOf(1),
+                    removed = emptySet(),
+                )
+            )
+    }
+
+    @Test
+    fun fromEmptySet() = runBlocking {
+        assertThatFlow(flowOf(emptySet(), setOf(1, 2)).setChanges())
+            .emitsExactly(
+                SetChanges(
+                    removed = emptySet(),
+                    added = setOf(1, 2),
+                )
+            )
+    }
+}
+
+private fun <T> assertThatFlow(flow: Flow<T>) = object {
+    suspend fun emitsExactly(vararg emissions: T) =
+        assertThat(flow.toList()).containsExactly(*emissions).inOrder()
+    suspend fun emitsNothing() =
+        assertThat(flow.toList()).isEmpty()
+}
+
+private fun assertThatJob(job: Job) = object {
+    fun isCompleted() = assertThat(job.isCompleted).isTrue()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 312db2d..2e74bf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -85,6 +85,8 @@
     @Mock
     MediaOutputDialogFactory mMediaOutputDialogFactory;
     @Mock
+    VolumePanelFactory mVolumePanelFactory;
+    @Mock
     ActivityStarter mActivityStarter;
     @Mock
     InteractionJankMonitor mInteractionJankMonitor;
@@ -102,6 +104,7 @@
                 mDeviceProvisionedController,
                 mConfigurationController,
                 mMediaOutputDialogFactory,
+                mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor);
         mDialog.init(0, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 5d63632..09da52e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -99,7 +99,6 @@
 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.legacy.NotificationGroupManagerLegacy;
 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.render.NotificationVisibilityProvider;
@@ -162,8 +161,6 @@
     @Mock
     private CommonNotifCollection mCommonNotifCollection;
     @Mock
-    private NotificationGroupManagerLegacy mNotificationGroupManager;
-    @Mock
     private BubblesManager.NotifCallback mNotifCallback;
     @Mock
     private WindowManager mWindowManager;
@@ -389,7 +386,6 @@
                 interruptionStateProvider,
                 mZenModeController,
                 mLockscreenUserManager,
-                mNotificationGroupManager,
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index b53ad0a..c56fdb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -16,14 +16,12 @@
 
 package com.android.systemui.flags
 
-import android.util.SparseArray
-import android.util.SparseBooleanArray
-import androidx.core.util.containsKey
-
 class FakeFeatureFlags : FeatureFlags {
-    private val booleanFlags = SparseBooleanArray()
-    private val stringFlags = SparseArray<String>()
+    private val booleanFlags = mutableMapOf<Int, Boolean>()
+    private val stringFlags = mutableMapOf<Int, String>()
     private val knownFlagNames = mutableMapOf<Int, String>()
+    private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
+    private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
 
     init {
         Flags.getFlagFields().forEach { field ->
@@ -33,27 +31,52 @@
     }
 
     fun set(flag: BooleanFlag, value: Boolean) {
-        booleanFlags.put(flag.id, value)
+        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+            notifyFlagChanged(flag)
+        }
     }
 
     fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
-        booleanFlags.put(flag.id, value)
+        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+            notifyFlagChanged(flag)
+        }
     }
 
     fun set(flag: ResourceBooleanFlag, value: Boolean) {
-        booleanFlags.put(flag.id, value)
+        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+            notifyFlagChanged(flag)
+        }
     }
 
     fun set(flag: SysPropBooleanFlag, value: Boolean) {
-        booleanFlags.put(flag.id, value)
+        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+            notifyFlagChanged(flag)
+        }
     }
 
     fun set(flag: StringFlag, value: String) {
-        stringFlags.put(flag.id, value)
+        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+            notifyFlagChanged(flag)
+        }
     }
 
     fun set(flag: ResourceStringFlag, value: String) {
-        stringFlags.put(flag.id, value)
+        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+            notifyFlagChanged(flag)
+        }
+    }
+
+    private fun notifyFlagChanged(flag: Flag<*>) {
+        flagListeners[flag.id]?.let { listeners ->
+            listeners.forEach { listener ->
+                listener.onFlagChanged(
+                    object : FlagListenable.FlagEvent {
+                        override val flagId = flag.id
+                        override fun requestNoRestart() {}
+                    }
+                )
+            }
+        }
     }
 
     override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
@@ -70,25 +93,30 @@
 
     override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
 
-    override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+    override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
+        flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
+        listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
+    }
 
-    override fun removeListener(listener: FlagListenable.Listener) {}
+    override fun removeListener(listener: FlagListenable.Listener) {
+        listenerFlagIds.remove(listener)?.let {
+                flagIds -> flagIds.forEach {
+                        id -> flagListeners[id]?.remove(listener)
+                }
+        }
+    }
 
     private fun flagName(flagId: Int): String {
         return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
     }
 
     private fun requireBooleanValue(flagId: Int): Boolean {
-        if (!booleanFlags.containsKey(flagId)) {
-            throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
-        }
         return booleanFlags[flagId]
+            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
     }
 
     private fun requireStringValue(flagId: Int): String {
-        if (!stringFlags.containsKey(flagId)) {
-            throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
-        }
         return stringFlags[flagId]
+            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
     }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6aa472f..01af23d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4248,7 +4248,8 @@
         final String procName = r.processName;
         HostingRecord hostingRecord = new HostingRecord(
                 HostingRecord.HOSTING_TYPE_SERVICE, r.instanceName,
-                r.definingPackageName, r.definingUid, r.serviceInfo.processName);
+                r.definingPackageName, r.definingUid, r.serviceInfo.processName,
+                getHostingRecordTriggerType(r));
         ProcessRecord app;
 
         if (!isolated) {
@@ -4358,6 +4359,14 @@
         return null;
     }
 
+    private String getHostingRecordTriggerType(ServiceRecord r) {
+        if (Manifest.permission.BIND_JOB_SERVICE.equals(r.permission)
+                && r.mRecentCallingUid == SYSTEM_UID) {
+            return HostingRecord.TRIGGER_TYPE_JOB;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
+
     private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
             throws TransactionTooLargeException {
         for (int i=r.bindings.size()-1; i>=0; i--) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 13a13f2..5e7d814 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -146,6 +146,7 @@
     static final String KEY_NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+    private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
     private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
@@ -325,6 +326,14 @@
      */
     private static final String KEY_PROCESS_KILL_TIMEOUT = "process_kill_timeout";
 
+    /**
+     * {@code true} to send in-flight alarm broadcasts ahead of non-alarms; {@code false}
+     * to queue alarm broadcasts identically to non-alarms [i.e. the pre-U behavior]; or
+     * {@code null} or empty string in order to fall back to whatever the build-time default
+     * was for the device.
+     */
+    private static final String KEY_PRIORITIZE_ALARM_BROADCASTS = "prioritize_alarm_broadcasts";
+
     private static final String KEY_DEFER_BOOT_COMPLETED_BROADCAST =
             "defer_boot_completed_broadcast";
 
@@ -667,6 +676,12 @@
             DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST;
 
     /**
+     * Whether alarm broadcasts are delivered immediately, or queued along with the rest
+     * of the pending ordered broadcasts.
+     */
+    volatile boolean mPrioritizeAlarmBroadcasts = DEFAULT_PRIORITIZE_ALARM_BROADCASTS;
+
+    /**
      * How long the Context.startForegroundService() grace period is to get around to
      * calling Service.startForeground() before we generate ANR.
      */
@@ -977,6 +992,9 @@
                             case KEY_PROCESS_KILL_TIMEOUT:
                                 updateProcessKillTimeout();
                                 break;
+                            case KEY_PRIORITIZE_ALARM_BROADCASTS:
+                                updatePrioritizeAlarmBroadcasts();
+                                break;
                             case KEY_DEFER_BOOT_COMPLETED_BROADCAST:
                                 updateDeferBootCompletedBroadcast();
                                 break;
@@ -1446,6 +1464,17 @@
         }
     }
 
+    private void updatePrioritizeAlarmBroadcasts() {
+        // Flag value can be something that evaluates to `true` or `false`,
+        // or empty/null.  If it's empty/null, the platform default is used.
+        final String flag = DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_PRIORITIZE_ALARM_BROADCASTS,
+                "");
+        mPrioritizeAlarmBroadcasts = TextUtils.isEmpty(flag)
+                ? DEFAULT_PRIORITIZE_ALARM_BROADCASTS
+                : Boolean.parseBoolean(flag);
+    }
     private void updateDeferBootCompletedBroadcast() {
         mDeferBootCompletedBroadcast = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1786,6 +1815,8 @@
         pw.print("="); pw.println(mComponentAliasOverrides);
         pw.print("  "); pw.print(KEY_DEFER_BOOT_COMPLETED_BROADCAST);
         pw.print("="); pw.println(mDeferBootCompletedBroadcast);
+        pw.print("  "); pw.print(KEY_PRIORITIZE_ALARM_BROADCASTS);
+        pw.print("="); pw.println(mPrioritizeAlarmBroadcasts);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED);
         pw.print("="); pw.println(mNoKillCachedProcessesUntilBootCompleted);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS);
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
index e9a36e0..4c44343 100644
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -181,7 +181,7 @@
                 for (int i = 0; i < numEntries; i++) {
                     if (recipientUid == mDeferredBroadcasts.get(i).uid) {
                         Deferrals d = mDeferredBroadcasts.remove(i);
-                        mAlarmBroadcasts.add(d);
+                        mAlarmDeferrals.add(d);
                         break;
                     }
                 }
@@ -201,10 +201,10 @@
 
                 // No longer an alarm target, so resume ordinary deferral policy
                 if (newCount <= 0) {
-                    final int numEntries = mAlarmBroadcasts.size();
+                    final int numEntries = mAlarmDeferrals.size();
                     for (int i = 0; i < numEntries; i++) {
-                        if (recipientUid == mAlarmBroadcasts.get(i).uid) {
-                            Deferrals d = mAlarmBroadcasts.remove(i);
+                        if (recipientUid == mAlarmDeferrals.get(i).uid) {
+                            Deferrals d = mAlarmDeferrals.remove(i);
                             insertLocked(mDeferredBroadcasts, d);
                             break;
                         }
@@ -234,7 +234,13 @@
     // General deferrals not holding up alarms
     private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
     // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
-    private final ArrayList<Deferrals> mAlarmBroadcasts = new ArrayList<>();
+    private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
+    // Under the "deliver alarm broadcasts immediately" policy, the queue of
+    // upcoming alarm broadcasts.  These are always delivered first - if the
+    // policy is changed on the fly from immediate-alarm-delivery to the previous
+    // in-order-queueing behavior, pending immediate alarm deliveries will drain
+    // and then the behavior settle into the pre-U semantics.
+    private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
 
     // Next outbound broadcast, established by getNextBroadcastLocked()
     private BroadcastRecord mCurrentBroadcast;
@@ -528,8 +534,9 @@
         synchronized (mLock) {
             return mCurrentBroadcast == null
                     && mOrderedBroadcasts.isEmpty()
+                    && mAlarmQueue.isEmpty()
                     && isDeferralsListEmpty(mDeferredBroadcasts)
-                    && isDeferralsListEmpty(mAlarmBroadcasts);
+                    && isDeferralsListEmpty(mAlarmDeferrals);
         }
     }
 
@@ -557,7 +564,13 @@
         }
         sb.append(mOrderedBroadcasts.size());
         sb.append(" ordered");
-        int n = pendingInDeferralsList(mAlarmBroadcasts);
+        int n = mAlarmQueue.size();
+        if (n > 0) {
+            sb.append(", ");
+            sb.append(n);
+            sb.append(" alarms");
+        }
+        n = pendingInDeferralsList(mAlarmDeferrals);
         if (n > 0) {
             sb.append(", ");
             sb.append(n);
@@ -592,8 +605,16 @@
     // ----------------------------------
     // BroadcastQueue operation support
     void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+        final ArrayList<BroadcastRecord> queue =
+                (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
+                        ? mAlarmQueue
+                        : mOrderedBroadcasts;
+
         if (r.receivers == null || r.receivers.isEmpty()) {
-            mOrderedBroadcasts.add(r);
+            // Fast no-op path for broadcasts that won't actually be dispatched to
+            // receivers - we still need to handle completion callbacks and historical
+            // records, but we don't need to consider the fancy cases.
+            queue.add(r);
             return;
         }
 
@@ -622,7 +643,8 @@
                 return;
             }
         } else {
-            mOrderedBroadcasts.add(r);
+            // Ordinary broadcast, so put it on the appropriate queue and carry on
+            queue.add(r);
         }
     }
 
@@ -652,10 +674,13 @@
     BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
         // Simple case, in the ordinary queue.
         BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
-
+        // ... or possibly in the simple alarm queue
+        if (old == null) {
+            old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
+        }
         // If we didn't find it, less-simple:  in a deferral queue?
         if (old == null) {
-            old = replaceDeferredBroadcastLocked(mAlarmBroadcasts, r, typeForLogging);
+            old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
         }
         if (old == null) {
             old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
@@ -706,6 +731,10 @@
         boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
                 packageName, filterByClasses, userId, doit);
         if (doit || !didSomething) {
+            didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
+                    packageName, filterByClasses, userId, doit);
+        }
+        if (doit || !didSomething) {
             ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
             for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
                 SparseArray<BroadcastRecord> brs =
@@ -731,7 +760,7 @@
                     packageName, filterByClasses, userId, doit);
         }
         if (doit || !didSomething) {
-            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmBroadcasts,
+            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
                     packageName, filterByClasses, userId, doit);
         }
         if (doit || !didSomething) {
@@ -781,12 +810,15 @@
         if (mCurrentBroadcast != null) {
             mCurrentBroadcast.dumpDebug(proto, fieldId);
         }
-        for (Deferrals d : mAlarmBroadcasts) {
+        for (Deferrals d : mAlarmDeferrals) {
             d.dumpDebug(proto, fieldId);
         }
         for (BroadcastRecord br : mOrderedBroadcasts) {
             br.dumpDebug(proto, fieldId);
         }
+        for (BroadcastRecord br : mAlarmQueue) {
+            br.dumpDebug(proto, fieldId);
+        }
         for (Deferrals d : mDeferredBroadcasts) {
             d.dumpDebug(proto, fieldId);
         }
@@ -816,23 +848,33 @@
             return mCurrentBroadcast;
         }
 
-        final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
         BroadcastRecord next = null;
 
+        // Alarms in flight take precedence over everything else.  This queue
+        // will be non-empty only when the relevant policy is in force, but if
+        // policy has changed on the fly we still need to drain this before we
+        // settle into the legacy behavior.
+        if (!mAlarmQueue.isEmpty()) {
+            next = mAlarmQueue.remove(0);
+        }
+
+        // Next in precedence are deferred BOOT_COMPLETED broadcasts
         if (next == null) {
             next = dequeueDeferredBootCompletedBroadcast();
         }
 
-        if (next == null && !mAlarmBroadcasts.isEmpty()) {
-            next = popLocked(mAlarmBroadcasts);
+        // Alarm-related deferrals are next in precedence...
+        if (next == null && !mAlarmDeferrals.isEmpty()) {
+            next = popLocked(mAlarmDeferrals);
             if (DEBUG_BROADCAST_DEFERRAL && next != null) {
                 Slog.i(TAG, "Next broadcast from alarm targets: " + next);
             }
         }
 
+        final boolean someQueued = !mOrderedBroadcasts.isEmpty();
+
         if (next == null && !mDeferredBroadcasts.isEmpty()) {
-            // We're going to deliver either:
+            // A this point we're going to deliver either:
             // 1. the next "overdue" deferral; or
             // 2. the next ordinary ordered broadcast; *or*
             // 3. the next not-yet-overdue deferral.
@@ -937,7 +979,7 @@
                     scheduleDeferralCheckLocked(true);
                 } else {
                     // alarm-related: strict order-encountered
-                    mAlarmBroadcasts.add(d);
+                    mAlarmDeferrals.add(d);
                 }
             } else {
                 // We're already deferring, but something was slow again.  Reset the
@@ -999,7 +1041,7 @@
      * immediately deliverable.  Used by the wait-for-broadcast-idle mechanism.
      */
     public void cancelDeferralsLocked() {
-        zeroDeferralTimes(mAlarmBroadcasts);
+        zeroDeferralTimes(mAlarmDeferrals);
         zeroDeferralTimes(mDeferredBroadcasts);
     }
 
@@ -1023,7 +1065,7 @@
         Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
         // ...but if not there, also check alarm-prioritized deferrals
         if (d == null) {
-            d = findUidLocked(uid, mAlarmBroadcasts);
+            d = findUidLocked(uid, mAlarmDeferrals);
         }
         return d;
     }
@@ -1035,7 +1077,7 @@
     private boolean removeDeferral(Deferrals d) {
         boolean didRemove = mDeferredBroadcasts.remove(d);
         if (!didRemove) {
-            didRemove = mAlarmBroadcasts.remove(d);
+            didRemove = mAlarmDeferrals.remove(d);
         }
         return didRemove;
     }
@@ -1103,14 +1145,20 @@
         } else {
             pw.println("  (null)");
         }
+        printed |= dumper.didPrint();
 
-        dumper.setHeading("Active ordered broadcasts");
-        dumper.setLabel("Active Ordered Broadcast");
-        for (Deferrals d : mAlarmBroadcasts) {
-            d.dumpLocked(dumper);
+        dumper.setHeading("Active alarm broadcasts");
+        dumper.setLabel("Active Alarm Broadcast");
+        for (BroadcastRecord br : mAlarmQueue) {
+            dumper.dump(br);
         }
         printed |= dumper.didPrint();
 
+        dumper.setHeading("Active ordered broadcasts");
+        dumper.setLabel("Active Ordered Broadcast");
+        for (Deferrals d : mAlarmDeferrals) {
+            d.dumpLocked(dumper);
+        }
         for (BroadcastRecord br : mOrderedBroadcasts) {
             dumper.dump(br);
         }
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 2498f76..30811a1 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -30,9 +30,10 @@
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
-import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
-import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
 
@@ -97,6 +98,7 @@
     public static final String TRIGGER_TYPE_ALARM = "alarm";
     public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
     public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
+    public static final String TRIGGER_TYPE_JOB = "job";
 
     @NonNull private final String mHostingType;
     private final String mHostingName;
@@ -126,10 +128,11 @@
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
-            String definingPackageName, int definingUid, String definingProcessName) {
+            String definingPackageName, int definingUid, String definingProcessName,
+            String triggerType) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
                 definingUid, false /* isTopApp */, definingProcessName, null /* action */,
-                TRIGGER_TYPE_UNKNOWN);
+                triggerType);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
@@ -313,9 +316,11 @@
             case TRIGGER_TYPE_ALARM:
                 return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
             case TRIGGER_TYPE_PUSH_MESSAGE:
-                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
             case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
-                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+            case TRIGGER_TYPE_JOB:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
             default:
                 return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
index de70fa0..028288f 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
@@ -220,7 +220,7 @@
             } else {
                 Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
                 sendStatusToCallback(clientStatusCallback,
-                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                        AmbientContextManager.STATUS_NOT_SUPPORTED);
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 66a4527..4adbfaa 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -53,8 +53,6 @@
 
     private static final String TAG = "AS.BtHelper";
 
-    private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
-
     private final @NonNull AudioDeviceBroker mDeviceBroker;
 
     BtHelper(@NonNull AudioDeviceBroker broker) {
@@ -913,7 +911,7 @@
                 return "ENCODING_APTX_HD";
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
                 return "ENCODING_LDAC";
-            case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS:
                 return "ENCODING_OPUS";
             default:
                 return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 689ddd2..c29755a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -72,7 +72,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
-import com.android.server.biometrics.sensors.CoexCoordinator;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -951,16 +950,6 @@
             return new ArrayList<>();
         }
 
-        public boolean isAdvancedCoexLogicEnabled(Context context) {
-            return Settings.Secure.getInt(context.getContentResolver(),
-                    CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0;
-        }
-
-        public boolean isCoexFaceNonBypassHapticsDisabled(Context context) {
-            return Settings.Secure.getInt(context.getContentResolver(),
-                    CoexCoordinator.FACE_HAPTIC_DISABLE, 0) != 0;
-        }
-
         public Supplier<Long> getRequestGenerator() {
             final AtomicLong generator = new AtomicLong(0);
             return () -> generator.incrementAndGet();
@@ -992,14 +981,6 @@
                 mEnabledOnKeyguardCallbacks);
         mRequestCounter = mInjector.getRequestGenerator();
 
-        // TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't
-        //  need to depend on context. We can remove this code once the advanced logic is enabled
-        //  by default.
-        CoexCoordinator coexCoordinator = CoexCoordinator.getInstance();
-        coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context));
-        coexCoordinator.setFaceHapticDisabledWhenNonBypass(
-                injector.isCoexFaceNonBypassHapticsDisabled(context));
-
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
                     new UserSwitchObserver() {
@@ -1333,7 +1314,5 @@
         pw.println();
         pw.println("CurrentSession: " + mAuthSession);
         pw.println();
-        pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString());
-        pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
new file mode 100644
index 0000000..62f94ed
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.log;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+
+import java.util.concurrent.TimeUnit;
+
+/** Probe for ambient light. */
+final class ALSProbe implements Probe {
+    private static final String TAG = "ALSProbe";
+
+    @Nullable
+    private final SensorManager mSensorManager;
+    @Nullable
+    private final Sensor mLightSensor;
+    @NonNull
+    private final Handler mTimer;
+    @DurationMillisLong
+    private long mMaxSubscriptionTime = -1;
+
+    private boolean mEnabled = false;
+    private boolean mDestroyed = false;
+    private volatile float mLastAmbientLux = -1;
+
+    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            mLastAmbientLux = event.values[0];
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+    /**
+     * Create a probe with a 1-minute max sampling time.
+     *
+     * @param sensorManager Sensor manager
+     */
+    ALSProbe(@NonNull SensorManager sensorManager) {
+        this(sensorManager, new Handler(Looper.getMainLooper()),
+                TimeUnit.MINUTES.toMillis(1));
+    }
+
+    /**
+     * Create a probe with a given max sampling time.
+     *
+     * Note: The max time is a workaround for potential scheduler bugs where
+     * {@link BaseClientMonitor#destroy()} is not called due to an abnormal lifecycle. Clients
+     * should ensure that {@link #disable()} and {@link #destroy()} are called appropriately and
+     * avoid relying on this timeout to unsubscribe from the sensor when it is not needed.
+     *
+     * @param sensorManager Sensor manager
+     * @param handler Timeout handler
+     * @param maxTime The max amount of time to subscribe to events. If this time is exceeded
+     *                {@link #disable()} will be called and no sampling will occur until {@link
+     *                #enable()} is called again.
+     */
+    @VisibleForTesting
+    ALSProbe(@Nullable SensorManager sensorManager, @NonNull Handler handler,
+            @DurationMillisLong long maxTime) {
+        mSensorManager = sensorManager;
+        mLightSensor = sensorManager != null
+                ? sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) : null;
+        mTimer = handler;
+        mMaxSubscriptionTime = maxTime;
+
+        if (mSensorManager == null || mLightSensor == null) {
+            Slog.w(TAG, "No sensor - probe disabled");
+            mDestroyed = true;
+        }
+    }
+
+    @Override
+    public synchronized void enable() {
+        if (!mDestroyed) {
+            enableLightSensorLoggingLocked();
+        }
+    }
+
+    @Override
+    public synchronized void disable() {
+        if (!mDestroyed) {
+            disableLightSensorLoggingLocked();
+        }
+    }
+
+    @Override
+    public synchronized void destroy() {
+        disable();
+        mDestroyed = true;
+    }
+
+    /** The most recent lux reading. */
+    public float getCurrentLux() {
+        return mLastAmbientLux;
+    }
+
+    private void enableLightSensorLoggingLocked() {
+        if (!mEnabled) {
+            mEnabled = true;
+            mLastAmbientLux = -1;
+            mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+            Slog.v(TAG, "Enable ALS: " + mLightSensorListener.hashCode());
+        }
+
+        resetTimerLocked(true /* start */);
+    }
+
+    private void disableLightSensorLoggingLocked() {
+        resetTimerLocked(false /* start */);
+
+        if (mEnabled) {
+            mEnabled = false;
+            mLastAmbientLux = -1;
+            mSensorManager.unregisterListener(mLightSensorListener);
+            Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
+        }
+    }
+
+    private void resetTimerLocked(boolean start) {
+        mTimer.removeCallbacksAndMessages(this /* token */);
+        if (start && mMaxSubscriptionTime > 0) {
+            mTimer.postDelayed(this::onTimeout, this /* token */, mMaxSubscriptionTime);
+        }
+    }
+
+    private void onTimeout() {
+        Slog.e(TAG, "Max time exceeded for ALS logger - disabling: "
+                + mLightSensorListener.hashCode());
+        disable();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index c86a8cb..8265203 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -46,6 +46,9 @@
     /** If the display is in AOD. */
     boolean isAod();
 
+    /** If the device is awake or is becoming awake. */
+    boolean isAwake();
+
     /**
      * Subscribe to context changes.
      *
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 9d2fde7..3d1a634 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -43,7 +43,7 @@
 /**
  * A default provider for {@link BiometricContext}.
  */
-class BiometricContextProvider implements BiometricContext {
+final class BiometricContextProvider implements BiometricContext {
 
     private static final String TAG = "BiometricContextProvider";
 
@@ -76,7 +76,8 @@
     private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
 
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
-    private boolean mIsDozing = false;
+    private boolean mIsAod = false;
+    private boolean mIsAwake = false;
 
     @VisibleForTesting
     BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
@@ -85,9 +86,14 @@
         try {
             service.setBiometicContextListener(new IBiometricContextListener.Stub() {
                 @Override
-                public void onDozeChanged(boolean isDozing) {
-                    mIsDozing = isDozing;
-                    notifyChanged();
+                public void onDozeChanged(boolean isDozing, boolean isAwake) {
+                    isDozing = isDozing && isAodEnabled();
+                    final boolean changed = (mIsAod != isDozing) || (mIsAwake != isAwake);
+                    if (changed) {
+                        mIsAod = isDozing;
+                        mIsAwake = isAwake;
+                        notifyChanged();
+                    }
                 }
 
                 private void notifyChanged() {
@@ -97,6 +103,10 @@
                         notifySubscribers();
                     }
                 }
+
+                private boolean isAodEnabled() {
+                    return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+                }
             });
             service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
                 @Override
@@ -161,7 +171,12 @@
 
     @Override
     public boolean isAod() {
-        return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+        return mIsAod;
+    }
+
+    @Override
+    public boolean isAwake() {
+        return mIsAwake;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 262be08..02b350e 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -17,21 +17,15 @@
 package com.android.server.biometrics.log;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
-import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.Utils;
@@ -43,61 +37,16 @@
 
     public static final String TAG = "BiometricLogger";
     public static final boolean DEBUG = false;
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static int sAlsCounter;
 
     private final int mStatsModality;
     private final int mStatsAction;
     private final int mStatsClient;
     private final BiometricFrameworkStatsLogger mSink;
-    @NonNull private final SensorManager mSensorManager;
+    @NonNull private final ALSProbe mALSProbe;
 
     private long mFirstAcquireTimeMs;
-    private boolean mLightSensorEnabled = false;
     private boolean mShouldLogMetrics = true;
 
-    private class ALSProbe implements Probe {
-        private boolean mDestroyed = false;
-
-        @Override
-        public synchronized void enable() {
-            if (!mDestroyed) {
-                setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
-            }
-        }
-
-        @Override
-        public synchronized void disable() {
-            if (!mDestroyed) {
-                setLightSensorLoggingEnabled(null);
-            }
-        }
-
-        @Override
-        public synchronized void destroy() {
-            disable();
-            mDestroyed = true;
-        }
-    }
-
-    // report only the most recent value
-    // consider com.android.server.display.utils.AmbientFilter or similar if need arises
-    private volatile float mLastAmbientLux = 0;
-
-    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            mLastAmbientLux = event.values[0];
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // Not used.
-        }
-    };
-
     /** Get a new logger with all unknown fields (for operations that do not require logs). */
     public static BiometricLogger ofUnknown(@NonNull Context context) {
         return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
@@ -105,6 +54,11 @@
     }
 
     /**
+     * Creates a new logger for an instance of a biometric operation.
+     *
+     * Do not reuse across operations. Instead, create a new one or use
+     * {@link #swapAction(Context, int)}.
+     *
      * @param context system_server context
      * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
      * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants.
@@ -125,7 +79,7 @@
         mStatsAction = statsAction;
         mStatsClient = statsClient;
         mSink = logSink;
-        mSensorManager = sensorManager;
+        mALSProbe = new ALSProbe(sensorManager);
     }
 
     /** Creates a new logger with the action replaced with the new action. */
@@ -136,6 +90,7 @@
     /** Disable logging metrics and only log critical events, such as system health issues. */
     public void disableMetrics() {
         mShouldLogMetrics = false;
+        mALSProbe.destroy();
     }
 
     /** {@link BiometricsProtoEnums} CLIENT_* constants */
@@ -265,7 +220,7 @@
                     + ", RequireConfirmation: " + requireConfirmation
                     + ", State: " + authState
                     + ", Latency: " + latency
-                    + ", Lux: " + mLastAmbientLux);
+                    + ", Lux: " + mALSProbe.getCurrentLux());
         } else {
             Slog.v(TAG, "Authentication latency: " + latency);
         }
@@ -276,7 +231,7 @@
 
         mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
                 Utils.isDebugEnabled(context, targetUserId),
-                latency, authState, requireConfirmation, targetUserId, mLastAmbientLux);
+                latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux());
     }
 
     /** Log enrollment outcome. */
@@ -290,7 +245,7 @@
                     + ", User: " + targetUserId
                     + ", Client: " + mStatsClient
                     + ", Latency: " + latency
-                    + ", Lux: " + mLastAmbientLux
+                    + ", Lux: " + mALSProbe.getCurrentLux()
                     + ", Success: " + enrollSuccessful);
         } else {
             Slog.v(TAG, "Enroll latency: " + latency);
@@ -301,7 +256,7 @@
         }
 
         mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
-                targetUserId, latency, enrollSuccessful, mLastAmbientLux);
+                targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux());
     }
 
     /** Report unexpected enrollment reported by the HAL. */
@@ -323,7 +278,9 @@
     }
 
     /**
-     * Get a callback to start/stop ALS capture when a client runs.
+     * Get a callback to start/stop ALS capture when the client runs. Do not create
+     * multiple callbacks since there is at most one light sensor (they will all share
+     * a single probe sampling from that sensor).
      *
      * If the probe should not run for the entire operation, do not set startWithClient and
      * start/stop the problem when needed.
@@ -331,53 +288,7 @@
      * @param startWithClient if probe should start automatically when the operation starts.
      */
     @NonNull
-    public CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) {
-        return new CallbackWithProbe<>(new ALSProbe(), startWithClient);
-    }
-
-    /** The sensor to use for ALS logging. */
-    @Nullable
-    protected Sensor getAmbientLightSensor(@NonNull SensorManager sensorManager) {
-        return mShouldLogMetrics ? sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) : null;
-    }
-
-    private void setLightSensorLoggingEnabled(@Nullable Sensor lightSensor) {
-        if (DEBUG) {
-            Slog.v(TAG, "capturing ambient light using: "
-                    + (lightSensor != null ? lightSensor : "[disabled]"));
-        }
-
-        if (lightSensor != null) {
-            if (!mLightSensorEnabled) {
-                mLightSensorEnabled = true;
-                mLastAmbientLux = 0;
-                int localAlsCounter;
-                synchronized (sLock) {
-                    localAlsCounter = sAlsCounter++;
-                }
-
-                if (localAlsCounter == 0) {
-                    mSensorManager.registerListener(mLightSensorListener, lightSensor,
-                            SensorManager.SENSOR_DELAY_NORMAL);
-                } else {
-                    Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS"
-                            + " counter: " + localAlsCounter);
-                    Slog.e(TAG, Log.getStackTraceString(new Throwable()));
-                }
-            }
-        } else {
-            mLightSensorEnabled = false;
-            mLastAmbientLux = 0;
-            mSensorManager.unregisterListener(mLightSensorListener);
-            int localAlsCounter;
-            synchronized (sLock) {
-                localAlsCounter = --sAlsCounter;
-            }
-            if (localAlsCounter != 0) {
-                Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: "
-                        + localAlsCounter);
-                Slog.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+    public CallbackWithProbe<Probe> getAmbientLightProbe(boolean startWithClient) {
+        return new CallbackWithProbe<>(mALSProbe, startWithClient);
     }
 }
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 4eb6d38..8a24ff6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.security.KeyStore;
 import android.util.EventLog;
 import android.util.Slog;
@@ -46,9 +45,7 @@
  * A class to keep track of the authentication state for a given client.
  */
 public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
-        implements AuthenticationConsumer  {
-
-    private static final String TAG = "Biometrics/AuthenticationClient";
+        implements AuthenticationConsumer {
 
     // New, has not started yet
     public static final int STATE_NEW = 0;
@@ -67,28 +64,27 @@
             STATE_STARTED_PAUSED_ATTEMPTED,
             STATE_STOPPED})
     @interface State {}
-
+    private static final String TAG = "Biometrics/AuthenticationClient";
+    protected final long mOperationId;
     private final boolean mIsStrongBiometric;
     private final boolean mRequireConfirmation;
     private final ActivityTaskManager mActivityTaskManager;
     private final BiometricManager mBiometricManager;
-    @Nullable private final TaskStackListener mTaskStackListener;
+    @Nullable
+    private final TaskStackListener mTaskStackListener;
     private final LockoutTracker mLockoutTracker;
     private final boolean mIsRestricted;
     private final boolean mAllowBackgroundAuthentication;
     private final boolean mIsKeyguardBypassEnabled;
-
-    protected final long mOperationId;
-
+    // TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
+    //  the state. We should think of a way to improve this in the future.
+    @State
+    protected int mState = STATE_NEW;
     private long mStartTimeMs;
 
     private boolean mAuthAttempted;
     private boolean mAuthSuccess = false;
 
-    // TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
-    //  the state. We should think of a way to improve this in the future.
-    protected @State int mState = STATE_NEW;
-
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
@@ -111,8 +107,9 @@
         mIsKeyguardBypassEnabled = isKeyguardBypassEnabled;
     }
 
-    public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
-        final @LockoutTracker.LockoutMode int lockoutMode =
+    @LockoutTracker.LockoutMode
+    public int handleFailedAttempt(int userId) {
+        @LockoutTracker.LockoutMode final int lockoutMode =
                 mLockoutTracker.getLockoutModeForUser(userId);
         final PerformanceTracker performanceTracker =
                 PerformanceTracker.getInstanceForSensorId(getSensorId());
@@ -173,14 +170,16 @@
 
         final ClientMonitorCallbackConverter listener = getListener();
 
-        if (DEBUG) Slog.v(TAG, "onAuthenticated(" + authenticated + ")"
-                + ", ID:" + identifier.getBiometricId()
-                + ", Owner: " + getOwnerString()
-                + ", isBP: " + isBiometricPrompt()
-                + ", listener: " + listener
-                + ", requireConfirmation: " + mRequireConfirmation
-                + ", user: " + getTargetUserId()
-                + ", clientMonitor: " + toString());
+        if (DEBUG) {
+            Slog.v(TAG, "onAuthenticated(" + authenticated + ")"
+                    + ", ID:" + identifier.getBiometricId()
+                    + ", Owner: " + getOwnerString()
+                    + ", isBP: " + isBiometricPrompt()
+                    + ", listener: " + listener
+                    + ", requireConfirmation: " + mRequireConfirmation
+                    + ", user: " + getTargetUserId()
+                    + ", clientMonitor: " + this);
+        }
 
         final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
         if (isCryptoOperation()) {
@@ -239,142 +238,57 @@
                         getSensorId(), getTargetUserId(), byteToken);
             }
 
-            final CoexCoordinator coordinator = CoexCoordinator.getInstance();
-            coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this,
-                    new CoexCoordinator.Callback() {
-                @Override
-                public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
-                    if (addAuthTokenIfStrong && mIsStrongBiometric) {
-                        final int result = KeyStore.getInstance().addAuthToken(byteToken);
-                        Slog.d(TAG, "addAuthToken: " + result);
+            // For BP, BiometricService will add the authToken to Keystore.
+            if (!isBiometricPrompt() && mIsStrongBiometric) {
+                final int result = KeyStore.getInstance().addAuthToken(byteToken);
+                if (result != KeyStore.NO_ERROR) {
+                    Slog.d(TAG, "Error adding auth token : " + result);
+                } else {
+                    Slog.d(TAG, "addAuthToken: " + result);
+                }
+            } else {
+                Slog.d(TAG, "Skipping addAuthToken");
+            }
+            try {
+                if (listener != null) {
+                    if (!mIsRestricted) {
+                        listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
+                                getTargetUserId(), mIsStrongBiometric);
                     } else {
-                        Slog.d(TAG, "Skipping addAuthToken");
+                        listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
+                                byteToken,
+                                getTargetUserId(), mIsStrongBiometric);
                     }
-
-                    if (listener != null) {
-                        try {
-                            // Explicitly have if/else here to make it super obvious in case the
-                            // code is touched in the future.
-                            if (!mIsRestricted) {
-                                listener.onAuthenticationSucceeded(getSensorId(),
-                                        identifier,
-                                        byteToken,
-                                        getTargetUserId(),
-                                        mIsStrongBiometric);
-                            } else {
-                                listener.onAuthenticationSucceeded(getSensorId(),
-                                        null /* identifier */,
-                                        byteToken,
-                                        getTargetUserId(),
-                                        mIsStrongBiometric);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Unable to notify listener", e);
-                        }
-                    } else {
-                        Slog.w(TAG, "Client not listening");
-                    }
+                } else {
+                    Slog.e(TAG, "Received successful auth, but client was not listening");
                 }
-
-                @Override
-                public void sendHapticFeedback() {
-                    if (listener != null && mShouldVibrate) {
-                        vibrateSuccess();
-                    }
-                }
-
-                @Override
-                public void handleLifecycleAfterAuth() {
-                    AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
-                }
-
-                @Override
-                public void sendAuthenticationCanceled() {
-                    sendCancelOnly(listener);
-                }
-            });
-        } else { // not authenticated
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to notify listener", e);
+                mCallback.onClientFinished(this, false);
+                return;
+            }
+        } else {
             if (isBackgroundAuth) {
                 Slog.e(TAG, "cancelling due to background auth");
                 cancel();
             } else {
                 // Allow system-defined limit of number of attempts before giving up
-                final @LockoutTracker.LockoutMode int lockoutMode =
+                @LockoutTracker.LockoutMode final int lockoutMode =
                         handleFailedAttempt(getTargetUserId());
                 if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
                     markAlreadyDone();
                 }
 
-                final CoexCoordinator coordinator = CoexCoordinator.getInstance();
-                coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
-                        new CoexCoordinator.Callback() {
-                            @Override
-                            public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
-                                if (listener != null) {
-                                    try {
-                                        listener.onAuthenticationFailed(getSensorId());
-                                    } catch (RemoteException e) {
-                                        Slog.e(TAG, "Unable to notify listener", e);
-                                    }
-                                }
-                            }
-
-                            @Override
-                            public void sendHapticFeedback() {
-                                if (listener != null && mShouldVibrate) {
-                                    vibrateError();
-                                }
-                            }
-
-                            @Override
-                            public void handleLifecycleAfterAuth() {
-                                AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
-                            }
-
-                            @Override
-                            public void sendAuthenticationCanceled() {
-                                sendCancelOnly(listener);
-                            }
-                        });
+                try {
+                    listener.onAuthenticationFailed(getSensorId());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify listener", e);
+                    mCallback.onClientFinished(this, false);
+                    return;
+                }
             }
         }
-    }
-
-    /**
-     * Only call this method on interfaces where lockout does not come from onError, I.E. the
-     * old HIDL implementation.
-     */
-    protected void onLockoutTimed(long durationMillis) {
-        final ClientMonitorCallbackConverter listener = getListener();
-        final CoexCoordinator coordinator = CoexCoordinator.getInstance();
-        coordinator.onAuthenticationError(this, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
-                new CoexCoordinator.ErrorCallback() {
-            @Override
-            public void sendHapticFeedback() {
-                if (listener != null && mShouldVibrate) {
-                    vibrateError();
-                }
-            }
-        });
-    }
-
-    /**
-     * Only call this method on interfaces where lockout does not come from onError, I.E. the
-     * old HIDL implementation.
-     */
-    protected void onLockoutPermanent() {
-        final ClientMonitorCallbackConverter listener = getListener();
-        final CoexCoordinator coordinator = CoexCoordinator.getInstance();
-        coordinator.onAuthenticationError(this,
-                BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
-                new CoexCoordinator.ErrorCallback() {
-            @Override
-            public void sendHapticFeedback() {
-                if (listener != null && mShouldVibrate) {
-                    vibrateError();
-                }
-            }
-        });
+        AuthenticationClient.this.handleLifecycleAfterAuth(authenticated);
     }
 
     private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
@@ -396,7 +310,7 @@
     public void onAcquired(int acquiredInfo, int vendorCode) {
         super.onAcquired(acquiredInfo, vendorCode);
 
-        final @LockoutTracker.LockoutMode int lockoutMode =
+        @LockoutTracker.LockoutMode final int lockoutMode =
                 mLockoutTracker.getLockoutModeForUser(getTargetUserId());
         if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
             PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
@@ -408,8 +322,6 @@
     public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
         mState = STATE_STOPPED;
-
-        CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError);
     }
 
     /**
@@ -419,7 +331,7 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
-        final @LockoutTracker.LockoutMode int lockoutMode =
+        @LockoutTracker.LockoutMode final int lockoutMode =
                 mLockoutTracker.getLockoutModeForUser(getTargetUserId());
         if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
             Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
@@ -450,22 +362,20 @@
     }
 
     /**
-     * Handles lifecycle, e.g. {@link BiometricScheduler},
-     * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication
-     * results are known. Note that this happens asynchronously from (but shortly after)
-     * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows
-     * {@link CoexCoordinator} a chance to invoke/delay this event.
-     * @param authenticated
+     * Handles lifecycle, e.g. {@link BiometricScheduler} after authentication. This is necessary
+     * as different clients handle the lifecycle of authentication success/reject differently. I.E.
+     * Fingerprint does not finish authentication when it is rejected.
      */
     protected abstract void handleLifecycleAfterAuth(boolean authenticated);
 
     /**
      * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
-     *         etc)
+     * etc)
      */
     public abstract boolean wasUserDetected();
 
-    public @State int getState() {
+    @State
+    public int getState() {
         return mState;
     }
 
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 63609f7..9317c4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -54,7 +54,7 @@
  * interactions with the HAL before finishing.
  *
  * We currently assume (and require) that each biometric sensor have its own instance of a
- * {@link BiometricScheduler}. See {@link CoexCoordinator}.
+ * {@link BiometricScheduler}.
  */
 @MainThread
 public class BiometricScheduler {
@@ -156,7 +156,6 @@
     private int mTotalOperationsHandled;
     private final int mRecentOperationsLimit;
     @NonNull private final List<Integer> mRecentOperations;
-    @NonNull private final CoexCoordinator mCoexCoordinator;
 
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
@@ -165,11 +164,6 @@
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
             Slog.d(getTag(), "[Started] " + clientMonitor);
-
-            if (clientMonitor instanceof AuthenticationClient) {
-                mCoexCoordinator.addAuthenticationClient(mSensorType,
-                        (AuthenticationClient<?>) clientMonitor);
-            }
         }
 
         @Override
@@ -189,10 +183,6 @@
                 }
 
                 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
-                if (clientMonitor instanceof AuthenticationClient) {
-                    mCoexCoordinator.removeAuthenticationClient(mSensorType,
-                            (AuthenticationClient<?>) clientMonitor);
-                }
 
                 if (mGestureAvailabilityDispatcher != null) {
                     mGestureAvailabilityDispatcher.markSensorActive(
@@ -216,8 +206,7 @@
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
-            int recentOperationsLimit,
-            @NonNull CoexCoordinator coexCoordinator) {
+            int recentOperationsLimit) {
         mBiometricTag = tag;
         mHandler = handler;
         mSensorType = sensorType;
@@ -227,7 +216,6 @@
         mCrashStates = new ArrayDeque<>();
         mRecentOperationsLimit = recentOperationsLimit;
         mRecentOperations = new ArrayList<>();
-        mCoexCoordinator = coexCoordinator;
     }
 
     /**
@@ -244,7 +232,7 @@
         this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
                 IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
+                LOG_NUM_RECENT_OPERATIONS);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
deleted file mode 100644
index c8a90e7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FACE;
-import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS;
-import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTypeToString;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricConstants;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.sensors.BiometricScheduler.SensorType;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-/**
- * Singleton that contains the core logic for determining if haptics and authentication callbacks
- * should be sent to receivers. Note that this class is used even when coex is not required (e.g.
- * single sensor devices, or multi-sensor devices where only a single sensor is authenticating).
- * This allows us to have all business logic in one testable place.
- */
-public class CoexCoordinator {
-
-    private static final String TAG = "BiometricCoexCoordinator";
-    public static final String SETTING_ENABLE_NAME =
-            "com.android.server.biometrics.sensors.CoexCoordinator.enable";
-    public static final String FACE_HAPTIC_DISABLE =
-            "com.android.server.biometrics.sensors.CoexCoordinator.disable_face_haptics";
-    private static final boolean DEBUG = true;
-
-    // Successful authentications should be used within this amount of time.
-    static final long SUCCESSFUL_AUTH_VALID_DURATION_MS = 5000;
-
-    /**
-     * Callback interface notifying the owner of "results" from the CoexCoordinator's business
-     * logic for accept and reject.
-     */
-    interface Callback {
-        /**
-         * Requests the owner to send the result (success/reject) and any associated info to the
-         * receiver (e.g. keyguard, BiometricService, etc).
-         */
-        void sendAuthenticationResult(boolean addAuthTokenIfStrong);
-
-        /**
-         * Requests the owner to initiate a vibration for this event.
-         */
-        void sendHapticFeedback();
-
-        /**
-         * Requests the owner to handle the AuthenticationClient's lifecycle (e.g. finish and remove
-         * from scheduler if auth was successful).
-         */
-        void handleLifecycleAfterAuth();
-
-        /**
-         * Requests the owner to notify the caller that authentication was canceled.
-         */
-        void sendAuthenticationCanceled();
-    }
-
-    /**
-     * Callback interface notifying the owner of "results" from the CoexCoordinator's business
-     * logic for errors.
-     */
-    interface ErrorCallback {
-        /**
-         * Requests the owner to initiate a vibration for this event.
-         */
-        void sendHapticFeedback();
-    }
-
-    private static final CoexCoordinator sInstance = new CoexCoordinator();
-
-    @VisibleForTesting
-    public static class SuccessfulAuth {
-        final long mAuthTimestamp;
-        final @SensorType int mSensorType;
-        final AuthenticationClient<?> mAuthenticationClient;
-        final Callback mCallback;
-        final CleanupRunnable mCleanupRunnable;
-
-        public static class CleanupRunnable implements Runnable {
-            @NonNull final LinkedList<SuccessfulAuth> mSuccessfulAuths;
-            @NonNull final SuccessfulAuth mAuth;
-            @NonNull final Callback mCallback;
-
-            public CleanupRunnable(@NonNull LinkedList<SuccessfulAuth> successfulAuths,
-                    @NonNull SuccessfulAuth auth, @NonNull Callback callback) {
-                mSuccessfulAuths = successfulAuths;
-                mAuth = auth;
-                mCallback = callback;
-            }
-
-            @Override
-            public void run() {
-                final boolean removed = mSuccessfulAuths.remove(mAuth);
-                Slog.w(TAG, "Removing stale successfulAuth: " + mAuth.toString()
-                        + ", success: " + removed);
-                mCallback.handleLifecycleAfterAuth();
-            }
-        }
-
-        public SuccessfulAuth(@NonNull Handler handler,
-                @NonNull LinkedList<SuccessfulAuth> successfulAuths,
-                long currentTimeMillis,
-                @SensorType int sensorType,
-                @NonNull AuthenticationClient<?> authenticationClient,
-                @NonNull Callback callback) {
-            mAuthTimestamp = currentTimeMillis;
-            mSensorType = sensorType;
-            mAuthenticationClient = authenticationClient;
-            mCallback = callback;
-
-            mCleanupRunnable = new CleanupRunnable(successfulAuths, this, callback);
-
-            handler.postDelayed(mCleanupRunnable, SUCCESSFUL_AUTH_VALID_DURATION_MS);
-        }
-
-        @Override
-        public String toString() {
-            return "SensorType: " + sensorTypeToString(mSensorType)
-                    + ", mAuthTimestamp: " + mAuthTimestamp
-                    + ", authenticationClient: " + mAuthenticationClient;
-        }
-    }
-
-    /** The singleton instance. */
-    @NonNull
-    public static CoexCoordinator getInstance() {
-        return sInstance;
-    }
-
-    @VisibleForTesting
-    public void setAdvancedLogicEnabled(boolean enabled) {
-        mAdvancedLogicEnabled = enabled;
-    }
-
-    public void setFaceHapticDisabledWhenNonBypass(boolean disabled) {
-        mFaceHapticDisabledWhenNonBypass = disabled;
-    }
-
-    @VisibleForTesting
-    void reset() {
-        mClientMap.clear();
-    }
-
-    // SensorType to AuthenticationClient map
-    private final Map<Integer, AuthenticationClient<?>> mClientMap = new HashMap<>();
-    @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths = new LinkedList<>();
-    private boolean mAdvancedLogicEnabled;
-    private boolean mFaceHapticDisabledWhenNonBypass;
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    private CoexCoordinator() {}
-
-    public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
-            @NonNull AuthenticationClient<?> client) {
-        if (DEBUG) {
-            Slog.d(TAG, "addAuthenticationClient(" + sensorTypeToString(sensorType) + ")"
-                    + ", client: " + client);
-        }
-
-        if (mClientMap.containsKey(sensorType)) {
-            Slog.w(TAG, "Overwriting existing client: " + mClientMap.get(sensorType)
-                    + " with new client: " + client);
-        }
-
-        mClientMap.put(sensorType, client);
-    }
-
-    public void removeAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
-            @NonNull AuthenticationClient<?> client) {
-        if (DEBUG) {
-            Slog.d(TAG, "removeAuthenticationClient(" + sensorTypeToString(sensorType) + ")"
-                    + ", client: " + client);
-        }
-
-        if (!mClientMap.containsKey(sensorType)) {
-            Slog.e(TAG, "sensorType: " + sensorType + " does not exist in map. Client: " + client);
-            return;
-        }
-        mClientMap.remove(sensorType);
-    }
-
-    /**
-     * Notify the coordinator that authentication succeeded (accepted)
-     */
-    public void onAuthenticationSucceeded(long currentTimeMillis,
-            @NonNull AuthenticationClient<?> client,
-            @NonNull Callback callback) {
-        final boolean isUsingSingleModality = isSingleAuthOnly(client);
-
-        if (client.isBiometricPrompt()) {
-            if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) {
-                // only send feedback on the first one
-            } else {
-                callback.sendHapticFeedback();
-            }
-            // For BP, BiometricService will add the authToken to Keystore.
-            callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
-            callback.handleLifecycleAfterAuth();
-        } else if (isUnknownClient(client)) {
-            // Client doesn't exist in our map for some reason. Give the user feedback so the
-            // device doesn't feel like it's stuck. All other cases below can assume that the
-            // client exists in our map.
-            callback.sendHapticFeedback();
-            callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-            callback.handleLifecycleAfterAuth();
-        } else if (mAdvancedLogicEnabled && client.isKeyguard()) {
-            if (isUsingSingleModality) {
-                // Single sensor authentication
-                callback.sendHapticFeedback();
-                callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-                callback.handleLifecycleAfterAuth();
-            } else {
-                // Multi sensor authentication
-                AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
-                AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
-                if (isCurrentFaceAuth(client)) {
-                    if (isUdfpsActivelyAuthing(udfps)) {
-                        // Face auth success while UDFPS is actively authing. No callback, no haptic
-                        // Feedback will be provided after UDFPS result:
-                        // 1) UDFPS succeeds - simply remove this from the queue
-                        // 2) UDFPS rejected - use this face auth success to notify clients
-                        mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths,
-                                currentTimeMillis, SENSOR_TYPE_FACE, client, callback));
-                    } else {
-                        if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
-                            Slog.w(TAG, "Skipping face success haptic");
-                        } else {
-                            callback.sendHapticFeedback();
-                        }
-                        callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-                        callback.handleLifecycleAfterAuth();
-                    }
-                } else if (isCurrentUdfps(client)) {
-                    if (isFaceScanning()) {
-                        // UDFPS succeeds while face is still scanning
-                        // Cancel face auth and/or prevent it from invoking haptics/callbacks after
-                        face.cancel();
-                    }
-
-                    removeAndFinishAllFaceFromQueue();
-
-                    callback.sendHapticFeedback();
-                    callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-                    callback.handleLifecycleAfterAuth();
-                } else {
-                    // Capacitive fingerprint sensor (or other)
-                    callback.sendHapticFeedback();
-                    callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-                    callback.handleLifecycleAfterAuth();
-                }
-            }
-        } else {
-            // Non-keyguard authentication. For example, Fingerprint Settings use of
-            // FingerprintManager for highlighting fingers
-            callback.sendHapticFeedback();
-            callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-            callback.handleLifecycleAfterAuth();
-        }
-    }
-
-    /**
-     * Notify the coordinator that a rejection has occurred.
-     */
-    public void onAuthenticationRejected(long currentTimeMillis,
-            @NonNull AuthenticationClient<?> client,
-            @LockoutTracker.LockoutMode int lockoutMode,
-            @NonNull Callback callback) {
-        final boolean isUsingSingleModality = isSingleAuthOnly(client);
-
-        if (mAdvancedLogicEnabled && client.isKeyguard()) {
-            if (isUsingSingleModality) {
-                callback.sendHapticFeedback();
-                callback.handleLifecycleAfterAuth();
-            } else {
-                // Multi sensor authentication
-                AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
-                AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
-                if (isCurrentFaceAuth(client)) {
-                    if (isUdfpsActivelyAuthing(udfps)) {
-                        // UDFPS should still be running in this case, do not vibrate. However, we
-                        // should notify the callback and finish the client, so that Keyguard and
-                        // BiometricScheduler do not get stuck.
-                        Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
-                        callback.handleLifecycleAfterAuth();
-                    } else if (isUdfpsAuthAttempted(udfps)) {
-                        // If UDFPS is STATE_STARTED_PAUSED (e.g. finger rejected but can still
-                        // auth after pointer goes down, it means UDFPS encountered a rejection. In
-                        // this case, we need to play the final reject haptic since face auth is
-                        // also done now.
-                        callback.sendHapticFeedback();
-                        callback.handleLifecycleAfterAuth();
-                    } else {
-                        // UDFPS auth has never been attempted.
-                        if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
-                            Slog.w(TAG, "Skipping face reject haptic");
-                        } else {
-                            callback.sendHapticFeedback();
-                        }
-                        callback.handleLifecycleAfterAuth();
-                    }
-                } else if (isCurrentUdfps(client)) {
-                    // Face should either be running, or have already finished
-                    SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis);
-                    if (auth != null) {
-                        Slog.d(TAG, "Using recent auth: " + auth);
-                        callback.handleLifecycleAfterAuth();
-
-                        auth.mCallback.sendHapticFeedback();
-                        auth.mCallback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-                        auth.mCallback.handleLifecycleAfterAuth();
-                    } else {
-                        Slog.d(TAG, "UDFPS rejected in multi-sensor auth");
-                        callback.sendHapticFeedback();
-                        callback.handleLifecycleAfterAuth();
-                    }
-                } else {
-                    Slog.d(TAG, "Unknown client rejected: " + client);
-                    callback.sendHapticFeedback();
-                    callback.handleLifecycleAfterAuth();
-                }
-            }
-        } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
-            if (!isCurrentFaceAuth(client)) {
-                callback.sendHapticFeedback();
-            }
-            callback.handleLifecycleAfterAuth();
-        } else {
-            callback.sendHapticFeedback();
-            callback.handleLifecycleAfterAuth();
-        }
-
-        // Always notify keyguard, otherwise the cached "running" state in KeyguardUpdateMonitor
-        // will get stuck.
-        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
-            // Don't send onAuthenticationFailed if we're in lockout, it causes a
-            // janky UI on Keyguard/BiometricPrompt since "authentication failed"
-            // will show briefly and be replaced by "device locked out" message.
-            callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
-        }
-    }
-
-    /**
-     * Notify the coordinator that an error has occurred.
-     */
-    public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
-            @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
-        final boolean isUsingSingleModality = isSingleAuthOnly(client);
-
-        // Figure out non-coex state
-        final boolean shouldUsuallyVibrate;
-        if (isCurrentFaceAuth(client)) {
-            final boolean notDetectedOnKeyguard = client.isKeyguard() && !client.wasUserDetected();
-            final boolean authAttempted = client.wasAuthAttempted();
-
-            switch (error) {
-                case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
-                case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
-                case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
-                    shouldUsuallyVibrate = authAttempted && !notDetectedOnKeyguard;
-                    break;
-                default:
-                    shouldUsuallyVibrate = false;
-                    break;
-            }
-        } else {
-            shouldUsuallyVibrate = false;
-        }
-
-        // Figure out coex state
-        final boolean hapticSuppressedByCoex;
-        if (mAdvancedLogicEnabled && client.isKeyguard()) {
-            if (isUsingSingleModality) {
-                hapticSuppressedByCoex = false;
-            } else {
-                hapticSuppressedByCoex = isCurrentFaceAuth(client)
-                        && !client.isKeyguardBypassEnabled();
-            }
-        } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
-            hapticSuppressedByCoex = isCurrentFaceAuth(client);
-        } else {
-            hapticSuppressedByCoex = false;
-        }
-
-        // Combine and send feedback if appropriate
-        if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
-            callback.sendHapticFeedback();
-        } else {
-            Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate
-                    + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
-        }
-    }
-
-    @Nullable
-    private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) {
-        for (SuccessfulAuth auth : mSuccessfulAuths) {
-            if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) {
-                // TODO(b/193089985): This removes the auth but does not notify the client with
-                //  an appropriate lifecycle event (such as ERROR_CANCELED), and violates the
-                //  API contract. However, this might be OK for now since the validity duration
-                //  is way longer than the time it takes to auth with fingerprint.
-                Slog.e(TAG, "Removing stale auth: " + auth);
-                mSuccessfulAuths.remove(auth);
-            } else if (auth.mSensorType == SENSOR_TYPE_FACE) {
-                mSuccessfulAuths.remove(auth);
-                return auth;
-            }
-        }
-        return null;
-    }
-
-    private void removeAndFinishAllFaceFromQueue() {
-        // Note that these auth are all successful, but have never notified the client (e.g.
-        // keyguard). To comply with the authentication lifecycle, we must notify the client that
-        // auth is "done". The safest thing to do is to send ERROR_CANCELED.
-        for (SuccessfulAuth auth : mSuccessfulAuths) {
-            if (auth.mSensorType == SENSOR_TYPE_FACE) {
-                Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth);
-                auth.mCallback.sendAuthenticationCanceled();
-                auth.mCallback.handleLifecycleAfterAuth();
-                mSuccessfulAuths.remove(auth);
-            }
-        }
-    }
-
-    private boolean isCurrentFaceAuth(@NonNull AuthenticationClient<?> client) {
-        return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
-    }
-
-    private boolean isCurrentUdfps(@NonNull AuthenticationClient<?> client) {
-        return client == mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
-    }
-
-    private boolean isFaceScanning() {
-        AuthenticationClient<?> client = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
-        return client != null && client.getState() == AuthenticationClient.STATE_STARTED;
-    }
-
-    private static boolean isUdfpsActivelyAuthing(@Nullable AuthenticationClient<?> client) {
-        if (client instanceof Udfps) {
-            return client.getState() == AuthenticationClient.STATE_STARTED;
-        }
-        return false;
-    }
-
-    private static boolean isUdfpsAuthAttempted(@Nullable AuthenticationClient<?> client) {
-        if (client instanceof Udfps) {
-            return client.getState() == AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED;
-        }
-        return false;
-    }
-
-    private boolean isUnknownClient(@NonNull AuthenticationClient<?> client) {
-        for (AuthenticationClient<?> c : mClientMap.values()) {
-            if (c == client) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean isSingleAuthOnly(@NonNull AuthenticationClient<?> client) {
-        if (mClientMap.values().size() != 1) {
-            return false;
-        }
-
-        for (AuthenticationClient<?> c : mClientMap.values()) {
-            if (c != client) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean hasMultipleSuccessfulAuthentications() {
-        int count = 0;
-        for (AuthenticationClient<?> c : mClientMap.values()) {
-            if (c.wasAuthSuccessful()) {
-                count++;
-            }
-            if (count > 1) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("Enabled: ").append(mAdvancedLogicEnabled);
-        sb.append(", Face Haptic Disabled: ").append(mFaceHapticDisabledWhenNonBypass);
-        sb.append(", Queue size: " ).append(mSuccessfulAuths.size());
-        for (SuccessfulAuth auth : mSuccessfulAuths) {
-            sb.append(", Auth: ").append(auth.toString());
-        }
-
-        return sb.toString();
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index ae75b7d..a486d16 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -95,10 +95,9 @@
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
             @NonNull CurrentUserRetriever currentUserRetriever,
-            @NonNull UserSwitchCallback userSwitchCallback,
-            @NonNull CoexCoordinator coexCoordinator) {
+            @NonNull UserSwitchCallback userSwitchCallback) {
         super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
-                LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
+                LOG_NUM_RECENT_OPERATIONS);
 
         mCurrentUserRetriever = currentUserRetriever;
         mUserSwitchCallback = userSwitchCallback;
@@ -112,7 +111,7 @@
         this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
                 IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
+                currentUserRetriever, userSwitchCallback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index d0c58fd..e18e31ec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -131,7 +131,7 @@
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
     }
 
     @Override
@@ -274,7 +274,6 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        super.onLockoutTimed(durationMillis);
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
@@ -290,7 +289,6 @@
 
     @Override
     public void onLockoutPermanent() {
-        super.onLockoutPermanent();
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index da78536..5d62cde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -115,7 +115,7 @@
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return new ClientMonitorCompositeCallback(mPreviewHandleDeleterCallback,
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 1935a5b..9baca98 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -101,7 +101,7 @@
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 226e458..16d2f7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -75,7 +75,7 @@
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index b530c8d..f7d94c9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -137,7 +137,7 @@
         mLockoutCache = lockoutCache;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
-        mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+        mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
 
         mWaitForAuthKeyguard =
@@ -315,21 +315,27 @@
     private ICancellationSignal doAuthenticate() throws RemoteException {
         final AidlSession session = getFreshDaemon();
 
+        final OperationContext opContext = getOperationContext();
+        getBiometricContext().subscribe(opContext, ctx -> {
+            if (session.hasContextMethods()) {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }
+
+            // TODO(b/243836005): this should come via ctx
+            final boolean isAwake = getBiometricContext().isAwake();
+            if (isAwake) {
+                mALSProbeCallback.getProbe().enable();
+            } else {
+                mALSProbeCallback.getProbe().disable();
+            }
+        });
+
         if (session.hasContextMethods()) {
-            final OperationContext opContext = getOperationContext();
-            final ICancellationSignal cancel =
-                    session.getSession().authenticateWithContext(mOperationId, opContext);
-            getBiometricContext()
-                    .subscribe(
-                            opContext,
-                            ctx -> {
-                                try {
-                                    session.getSession().onContextChanged(ctx);
-                                } catch (RemoteException e) {
-                                    Slog.e(TAG, "Unable to notify context changed", e);
-                                }
-                            });
-            return cancel;
+            return session.getSession().authenticateWithContext(mOperationId, opContext);
         } else {
             return session.getSession().authenticate(mOperationId);
         }
@@ -360,7 +366,6 @@
         try {
             mIsPointerDown = true;
             mState = STATE_STARTED;
-            mALSProbeCallback.getProbe().enable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
@@ -389,7 +394,6 @@
         try {
             mIsPointerDown = false;
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
-            mALSProbeCallback.getProbe().disable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
@@ -424,7 +428,6 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        super.onLockoutTimed(durationMillis);
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
@@ -448,7 +451,6 @@
 
     @Override
     public void onLockoutPermanent() {
-        super.onLockoutPermanent();
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index f4f0a19..e0393b5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -94,7 +94,7 @@
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
-        mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+        mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -216,7 +216,6 @@
     public void onPointerDown(int x, int y, float minor, float major) {
         try {
             mIsPointerDown = true;
-            mALSProbeCallback.getProbe().enable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
@@ -240,7 +239,6 @@
     public void onPointerUp() {
         try {
             mIsPointerDown = false;
-            mALSProbeCallback.getProbe().disable();
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 97fbb5f..7ed1a51 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -86,7 +86,7 @@
         mLockoutFrameworkImpl = lockoutTracker;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
-        mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+        mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 2a59c8c..5d9af53 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -84,7 +84,7 @@
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return new ClientMonitorCompositeCallback(
-                getLogger().createALSCallback(true /* startWithClient */), callback);
+                getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 25d0752..c835d2f 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -116,8 +116,10 @@
             luxLevels = getLuxLevels(resources.getIntArray(
                     com.android.internal.R.array.config_autoBrightnessLevelsIdle));
         } else {
-            brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
-            luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
+            brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+                    com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+            luxLevels = getLuxLevels(resources.getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLevels));
         }
 
         // Display independent, mode independent values
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 3b627ef..4f3fd64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
 import android.os.Environment;
@@ -150,22 +149,12 @@
  *      </quirks>
  *
  *      <autoBrightness>
- *          <brighteningLightDebounceMillis>
+ *           <brighteningLightDebounceMillis>
  *              2000
- *          </brighteningLightDebounceMillis>
+ *           </brighteningLightDebounceMillis>
  *          <darkeningLightDebounceMillis>
  *              1000
  *          </darkeningLightDebounceMillis>
- *          <displayBrightnessMapping>
- *              <displayBrightnessPoint>
- *                  <lux>50</lux>
- *                  <nits>45</nits>
- *              </displayBrightnessPoint>
- *              <displayBrightnessPoint>
- *                  <lux>80</lux>
- *                  <nits>75</nits>
- *              </displayBrightnessPoint>
- *          </displayBrightnessMapping>
  *      </autoBrightness>
  *
  *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -279,39 +268,6 @@
     // for the corresponding values above
     private float[] mBrightness;
 
-
-    /**
-     * Array of desired screen brightness in nits corresponding to the lux values
-     * in the mBrightnessLevelsLux array. The display brightness is defined as the
-     * measured brightness of an all-white image. The brightness values must be non-negative and
-     * non-decreasing. This must be overridden in platform specific overlays
-     */
-    private float[] mBrightnessLevelsNits;
-
-    /**
-     * Array of light sensor lux values to define our levels for auto backlight
-     * brightness support.
-     * The N entries of this array define N + 1 control points as follows:
-     * (1-based arrays)
-     *
-     * Point 1:            (0, value[1]):             lux <= 0
-     * Point 2:     (level[1], value[2]):  0        < lux <= level[1]
-     * Point 3:     (level[2], value[3]):  level[2] < lux <= level[3]
-     * ...
-     * Point N+1: (level[N], value[N+1]):  level[N] < lux
-     *
-     * The control points must be strictly increasing.  Each control point
-     * corresponds to an entry in the brightness backlight values arrays.
-     * For example, if lux == level[1] (first element of the levels array)
-     * then the brightness will be determined by value[2] (second element
-     * of the brightness values array).
-     *
-     * Spline interpolation is used to determine the auto-brightness
-     * backlight values for lux levels between these control points.
-     *
-     */
-    private float[] mBrightnessLevelsLux;
-
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
     private float mBrightnessDefault = Float.NaN;
@@ -705,20 +661,6 @@
         return mAutoBrightnessBrighteningLightDebounce;
     }
 
-    /**
-     * @return Auto brightness brightening ambient lux levels
-     */
-    public float[] getAutoBrightnessBrighteningLevelsLux() {
-        return mBrightnessLevelsLux;
-    }
-
-    /**
-     * @return Auto brightness brightening nits levels
-     */
-    public float[] getAutoBrightnessBrighteningLevelsNits() {
-        return mBrightnessLevelsNits;
-    }
-
     @Override
     public String toString() {
         return "DisplayDeviceConfig{"
@@ -761,8 +703,6 @@
                 + mAutoBrightnessBrighteningLightDebounce
                 + ", mAutoBrightnessDarkeningLightDebounce= "
                 + mAutoBrightnessDarkeningLightDebounce
-                + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
-                + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
                 + "}";
     }
 
@@ -839,7 +779,6 @@
         loadBrightnessRampsFromConfigXml();
         loadAmbientLightSensorFromConfigXml();
         setProxSensorUnspecified();
-        loadAutoBrightnessConfigsFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -1052,7 +991,6 @@
     private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
         loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
         loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
-        loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
     }
 
     /**
@@ -1085,33 +1023,6 @@
         }
     }
 
-    /**
-     * Loads the auto-brightness display brightness mappings. Internally, this takes care of
-     * loading the value from the display config, and if not present, falls back to config.xml.
-     */
-    private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
-        if (autoBrightnessConfig == null
-                || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
-            mBrightnessLevelsNits = getFloatArray(mContext.getResources()
-                    .obtainTypedArray(com.android.internal.R.array
-                            .config_autoBrightnessDisplayValuesNits));
-            mBrightnessLevelsLux = getFloatArray(mContext.getResources()
-                    .obtainTypedArray(com.android.internal.R.array
-                            .config_autoBrightnessLevels));
-        } else {
-            final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
-                    .getDisplayBrightnessPoint().size();
-            mBrightnessLevelsNits = new float[size];
-            mBrightnessLevelsLux = new float[size];
-            for (int i = 0; i < size; i++) {
-                mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getNits().floatValue();
-                mBrightnessLevelsLux[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getLux().floatValue();
-            }
-        }
-    }
-
     private void loadBrightnessMapFromConfigXml() {
         // Use the config.xml mapping
         final Resources res = mContext.getResources();
@@ -1337,10 +1248,6 @@
                 com.android.internal.R.string.config_displayLightSensorType);
     }
 
-    private void loadAutoBrightnessConfigsFromConfigXml() {
-        loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
-    }
-
     private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getLightSensor();
         if (sensorDetails != null) {
@@ -1483,22 +1390,6 @@
         }
     }
 
-    /**
-     * Extracts a float array from the specified {@link TypedArray}.
-     *
-     * @param array The array to convert.
-     * @return the given array as a float array.
-     */
-    public static float[] getFloatArray(TypedArray array) {
-        final int n = array.length();
-        float[] vals = new float[n];
-        for (int i = 0; i < n; i++) {
-            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
-        }
-        array.recycle();
-        return vals;
-    }
-
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS
index 9588fa9..87d30f3 100644
--- a/services/core/java/com/android/server/logcat/OWNERS
+++ b/services/core/java/com/android/server/logcat/OWNERS
@@ -1,5 +1,7 @@
 cbrubaker@google.com
 eunjeongshin@google.com
+georgechan@google.com
 jsharkey@google.com
 vishwath@google.com
 wenhaowang@google.com
+xiaozhenl@google.com
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index c12dc8e..a5c762a 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -119,7 +119,7 @@
         ComponentName componentName = getComponentName(pendingIntent, componentType);
         if (componentName != null) {
             if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
-                EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet Logging.
+                EventLog.writeEvent(0x534e4554, "238177121", -1, "");
                 throw new IllegalArgumentException("ComponentName does not belong to "
                         + "sessionPackageName. sessionPackageName = " + sessionPackageName
                         + ", ComponentName pkg = " + componentName.getPackageName());
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 4f8771a..1ee9a87 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -914,7 +914,16 @@
         }
 
         @Override
-        public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
+        public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
+                throws RemoteException {
+            //mPackageName has been verified in MediaSessionService.enforcePackageName().
+            if (!TextUtils.equals(sessionPackageName, mPackageName)) {
+                EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+                throw new IllegalArgumentException("sessionPackageName name does not match "
+                        + "package name provided to MediaSessionRecord. sessionPackageName = "
+                        + sessionPackageName + ", pkg = "
+                        + mPackageName);
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -922,7 +931,7 @@
                     return;
                 }
                 mMediaButtonReceiverHolder =
-                        MediaButtonReceiverHolder.create(mContext, mUserId, pi, mPackageName);
+                        MediaButtonReceiverHolder.create(mContext, mUserId, pi, sessionPackageName);
                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -936,7 +945,7 @@
                 //mPackageName has been verified in MediaSessionService.enforcePackageName().
                 if (receiver != null && !TextUtils.equals(
                         mPackageName, receiver.getPackageName())) {
-                    EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet Logging.
+                    EventLog.writeEvent(0x534e4554, "238177121", -1, "");
                     throw new IllegalArgumentException("receiver does not belong to "
                             + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
                             + ", Receiver Pkg = " + receiver.getPackageName());
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index d426679..4c23ab8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             String reason) {
-        if (!isSystemRule(automaticZenRule)) {
+        if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
                 component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,11 +582,6 @@
         }
     }
 
-    private boolean isSystemRule(AutomaticZenRule rule) {
-        return rule.getOwner() != null
-                && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
-    }
-
     private ServiceInfo getServiceInfo(ComponentName owner) {
         Intent queryIntent = new Intent();
         queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 297439f..8f89fdd 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -321,4 +321,15 @@
      * {@link UserManager#isUserVisible()} in the given display.
      */
     public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
+
+    /**
+     * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
+     * user is not assigned to any display.
+     *
+     * <p>The current foreground user is associated with the main display, while other users would
+     * only assigned to a display if they were started with
+     * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. If the user is a profile
+     * and is running, it's assigned to its parent display.
+     */
+    public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ce77c8b..3b898f8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1731,6 +1731,18 @@
         return false;
     }
 
+    // TODO(b/239982558): add unit test
+    private int getDisplayAssignedToUser(@UserIdInt int userId) {
+        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            return Display.DEFAULT_DISPLAY;
+        }
+        synchronized (mUsersLock) {
+            return mUsersOnSecondaryDisplays == null
+                    ? Display.INVALID_DISPLAY
+                    : mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
+        }
+    }
+
     private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
         int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
 
@@ -5099,12 +5111,9 @@
                 Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
 
-            // TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed?
-            if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
-                    && userData.info.isManagedProfile()) {
-                // Send broadcast to notify system that the user removed was a
-                // managed user.
-                sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id);
+            if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+                sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id,
+                        userData.info.userType);
             }
 
             if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
@@ -5331,11 +5340,43 @@
         }
     }
 
-    private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId) {
+    /**
+     * Send {@link Intent#ACTION_PROFILE_REMOVED} broadcast when a user of type
+     * {@link UserInfo#isProfile()} is removed. Additionally sends
+     * {@link Intent#ACTION_MANAGED_PROFILE_REMOVED} broadcast if the profile is of type
+     * {@link UserManager#USER_TYPE_PROFILE_MANAGED}
+     *
+     * <p> {@link Intent#ACTION_PROFILE_REMOVED} is a generalized broadcast for all users of type
+     *     {@link UserInfo#isProfile()} and is sent only to dynamic receivers.
+     *
+     * <p> In contrast, the {@link Intent#ACTION_MANAGED_PROFILE_REMOVED} broadcast is specific to
+     *     {@link UserManager#USER_TYPE_PROFILE_MANAGED} and is sent to both manifest and dynamic
+     *     receivers thus it is still needed as manifest receivers will not be able to listen to
+     *     the aforementioned generalized broadcast.
+     */
+    private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId, String userType) {
+        if (Objects.equals(userType, UserManager.USER_TYPE_PROFILE_MANAGED)) {
+            sendManagedProfileRemovedBroadcast(parentUserId, removedUserId);
+        }
+        sendProfileBroadcastToRegisteredReceivers(
+                new Intent(Intent.ACTION_PROFILE_REMOVED),
+                parentUserId, removedUserId);
+    }
+
+    private void sendProfileBroadcastToRegisteredReceivers(Intent intent,
+            int parentUserId, int userId) {
+        final UserHandle parentHandle = UserHandle.of(parentUserId);
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, parentHandle, /* receiverPermission= */null);
+    }
+
+    private void sendManagedProfileRemovedBroadcast(int parentUserId, int removedUserId) {
         Intent managedProfileIntent = new Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED);
         managedProfileIntent.putExtra(Intent.EXTRA_USER, new UserHandle(removedUserId));
         managedProfileIntent.putExtra(Intent.EXTRA_USER_HANDLE, removedUserId);
-        final UserHandle parentHandle = new UserHandle(parentUserId);
+        final UserHandle parentHandle = UserHandle.of(parentUserId);
         getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
                 managedProfileIntent, parentHandle, /* requiresPermission= */ false);
         managedProfileIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -6666,6 +6707,11 @@
         public boolean isUserVisible(int userId, int displayId) {
             return isUserVisibleOnDisplay(userId, displayId);
         }
+
+        @Override
+        public int getDisplayAssignedToUser(int userId) {
+            return UserManagerService.this.getDisplayAssignedToUser(userId);
+        }
     } // class LocalService
 
     /**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 09035cd..15c9ba9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -37,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
 
@@ -829,17 +830,41 @@
             @Override
             public void binderDied() {
                 // This is called whenever our client process dies.
+                SparseArray<ModelState.Activity> cachedMap =
+                        new SparseArray<ModelState.Activity>();
                 synchronized (SoundTriggerMiddlewareValidation.this) {
-                    try {
-                        // Gracefully stop all active recognitions and unload the models.
+                        // Copy the relevant state under the lock, so we can call back without
+                        // holding a lock. This exposes us to a potential race, but the client is
+                        // dead so we don't expect one.
+                        // TODO(240613068) A more resilient fix for this.
                         for (Map.Entry<Integer, ModelState> entry :
                                 mLoadedModels.entrySet()) {
-                            if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
-                                mDelegate.stopRecognition(entry.getKey());
-                            }
-                            mDelegate.unloadModel(entry.getKey());
+                            cachedMap.put(entry.getKey(), entry.getValue().activityState);
                         }
-                        // Detach.
+                }
+                try {
+                    // Gracefully stop all active recognitions and unload the models.
+                    for (int i = 0; i < cachedMap.size(); i++) {
+                        if (cachedMap.valueAt(i) == ModelState.Activity.ACTIVE) {
+                            mDelegate.stopRecognition(cachedMap.keyAt(i));
+                        }
+                        mDelegate.unloadModel(cachedMap.keyAt(i));
+                    }
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+                synchronized (SoundTriggerMiddlewareValidation.this) {
+                   // Check if state updated unexpectedly to log race conditions.
+                    for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+                        if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) {
+                            Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                        }
+                    }
+                    if (mLoadedModels.size() != cachedMap.size()) {
+                        Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                    }
+                    try {
+                        // Detach
                         detachInternal();
                     } catch (Exception e) {
                         throw handleException(e);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index d7bfd1d..fcf0a90 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -263,8 +263,10 @@
                     }
                 });
 
-        vdm.registerVirtualDisplayListener(mVirtualDeviceListener);
-        vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener);
+        if (vdm != null){
+          vdm.registerVirtualDisplayListener(mVirtualDeviceListener);
+          vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener);
+        }
 
         registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
         registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f8f94f6..58e1d05 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1582,13 +1582,6 @@
 
         if (newParent != null && isState(RESUMED)) {
             newParent.setResumedActivity(this, "onParentChanged");
-            if (mStartingWindow != null && mStartingData != null
-                    && mStartingData.mAssociatedTask == null && newParent.isEmbedded()) {
-                // The starting window should keep covering its task when the activity is
-                // reparented to a task fragment that may not fill the task bounds.
-                associateStartingDataWithTask();
-                attachStartingSurfaceToAssociatedTask();
-            }
             mImeInsetsFrozenUntilStartInput = false;
         }
 
@@ -2679,14 +2672,17 @@
         }
     }
 
+    /** Called when the starting window is added to this activity. */
     void attachStartingWindow(@NonNull WindowState startingWindow) {
         startingWindow.mStartingData = mStartingData;
         mStartingWindow = startingWindow;
+        // The snapshot type may have called associateStartingDataWithTask().
         if (mStartingData != null && mStartingData.mAssociatedTask != null) {
             attachStartingSurfaceToAssociatedTask();
         }
     }
 
+    /** Makes starting window always fill the associated task. */
     private void attachStartingSurfaceToAssociatedTask() {
         // Associate the configuration of starting window with the task.
         overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
@@ -2694,6 +2690,7 @@
                 mStartingData.mAssociatedTask.mSurfaceControl);
     }
 
+    /** Called when the starting window is not added yet but its data is known to fill the task. */
     private void associateStartingDataWithTask() {
         mStartingData.mAssociatedTask = task;
         task.forAllActivities(r -> {
@@ -2703,6 +2700,16 @@
         });
     }
 
+    /** Associates and attaches an added starting window to the current task. */
+    void associateStartingWindowWithTaskIfNeeded() {
+        if (mStartingWindow == null || mStartingData == null
+                || mStartingData.mAssociatedTask != null) {
+            return;
+        }
+        associateStartingDataWithTask();
+        attachStartingSurfaceToAssociatedTask();
+    }
+
     void removeStartingWindow() {
         boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 619d693..9ad62af 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1866,6 +1866,16 @@
         final ActivityRecord targetTaskTop = newTask
                 ? null : targetTask.getTopNonFinishingActivity();
         if (targetTaskTop != null) {
+            // Removes the existing singleInstance activity in another task (if any) while
+            // launching a singleInstance activity on sourceRecord's task.
+            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode && mSourceRecord != null
+                    && targetTask == mSourceRecord.getTask()) {
+                final ActivityRecord activity = mRootWindowContainer.findActivity(mIntent,
+                        mStartActivity.info, false);
+                if (activity != null && activity.getTask() != targetTask) {
+                    activity.destroyIfPossible("Removes redundant singleInstance");
+                }
+            }
             // Recycle the target task for this launch.
             startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
             if (startResult != START_SUCCESS) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1c90bba..6ee0186 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3188,8 +3188,12 @@
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mPointerEventDispatcher.dispose();
             setRotationAnimation(null);
+            // Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
+            // -> this DisplayContent.
+            setRemoteInsetsController(null);
             mWmService.mAnimator.removeDisplayLocked(mDisplayId);
             mOverlayLayer.release();
+            mWindowingLayer.release();
             mInputMonitor.onDisplayRemoved();
             mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
             mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e38f5fe..de135a3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1436,6 +1436,13 @@
         final TaskFragment childTaskFrag = child.asTaskFragment();
         if (childTaskFrag != null && childTaskFrag.asTask() == null) {
             childTaskFrag.setMinDimensions(mMinWidth, mMinHeight);
+
+            // The starting window should keep covering its task when a pure TaskFragment is added
+            // because its bounds may not fill the task.
+            final ActivityRecord top = getTopMostActivity();
+            if (top != null) {
+                top.associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 4078996..24101dd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -89,6 +89,7 @@
 
 cc_defaults {
     name: "libservices.core-libs",
+    defaults: ["android.hardware.graphics.common-ndk_shared"],
     shared_libs: [
         "libadb_pairing_server",
         "libadb_pairing_connection",
@@ -158,7 +159,6 @@
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.2",
-        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.mapper@4.0",
         "android.hardware.input.processor-V1-ndk",
         "android.hardware.ir@1.0",
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 0000000..477625a
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -0,0 +1,33 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:complexType name="autoBrightness">
+        <xs:sequence>
+            <!-- Sets the debounce for autoBrightness brightening in millis-->
+            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Sets the debounce for autoBrightness darkening in millis-->
+            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 98f83d8..bea5e2c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
 <xs:schema version="2.0"
            elementFormDefault="qualified"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:include schemaLocation="autobrightness.xsd" />
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
@@ -342,74 +343,4 @@
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
-
-    <xs:complexType name="autoBrightness">
-        <xs:sequence>
-            <!-- Sets the debounce for autoBrightness brightening in millis-->
-            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-            <!-- Sets the debounce for autoBrightness darkening in millis-->
-            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-            <!-- Sets the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-            <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
-    </xs:complexType>
-
-    <!-- Represents the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-    <xs:complexType name="displayBrightnessMapping">
-        <xs:sequence>
-            <!-- Sets the list of display brightness points, each representing the desired screen
-            brightness in nits to the corresponding lux for the current display
-
-            The N entries of this array define N + 1 control points as follows:
-            (1-based arrays)
-
-            Point 1:            (0, nits[1]):             currentLux <= 0
-            Point 2:     (lux[1], nits[2]):       0 < currentLux <= lux[1]
-            Point 3:     (lux[2], nits[3]):  lux[2] < currentLux <= lux[3]
-            ...
-            Point N+1: (lux[N], nits[N+1]):            lux[N] < currentLux
-
-            The control points must be strictly increasing. Each control point
-            corresponds to an entry in the brightness backlight values arrays.
-            For example, if currentLux == lux[1] (first element of the levels array)
-            then the brightness will be determined by nits[2] (second element
-            of the brightness values array).
-            -->
-            <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
-                        minOccurs="1" maxOccurs="unbounded">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
-    </xs:complexType>
-
-    <!-- Represents a point in the display brightness mapping, representing the lux level from the
-    light sensor to the desired screen brightness in nits at this level  -->
-    <xs:complexType name="displayBrightnessPoint">
-        <xs:sequence>
-            <!-- The lux level from the light sensor. This must be a non-negative integer -->
-            <xs:element name="lux" type="xs:nonNegativeInteger"
-                        minOccurs="1" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-
-            <!-- Desired screen brightness in nits corresponding to the suggested lux values.
-             The display brightness is defined as the measured brightness of an all-white image.
-             This must be a non-negative integer -->
-            <xs:element name="nits" type="xs:nonNegativeInteger"
-                        minOccurs="1" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
-    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e5d2617..e9a9269 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -5,10 +5,8 @@
     ctor public AutoBrightness();
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
-    method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
-    method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
   }
 
   public class BrightnessThresholds {
@@ -45,19 +43,6 @@
     method public java.util.List<com.android.server.display.config.Density> getDensity();
   }
 
-  public class DisplayBrightnessMapping {
-    ctor public DisplayBrightnessMapping();
-    method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
-  }
-
-  public class DisplayBrightnessPoint {
-    ctor public DisplayBrightnessPoint();
-    method public final java.math.BigInteger getLux();
-    method public final java.math.BigInteger getNits();
-    method public final void setLux(java.math.BigInteger);
-    method public final void setNits(java.math.BigInteger);
-  }
-
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
     method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index f454ac7..f1dc1fa 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -10,6 +10,8 @@
 cc_library_shared {
     name: "libmockingservicestestjni",
 
+    defaults: ["android.hardware.graphics.common-ndk_shared"],
+
     cflags: [
         "-Wall",
         "-Werror",
@@ -48,7 +50,6 @@
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.2",
-        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.mapper@4.0",
         "android.hidl.token@1.0-utils",
     ],
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
index 03eff2a..e170e98 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
@@ -258,7 +258,7 @@
                                 PROVIDER_A_SERVICE_A,
                                 PROVIDER_A_SERVICE_C));
         FakeGameServiceProviderInstance instanceB =
-                seedConfigurationForUser(USER_10, configurationA);
+                seedConfigurationForUser(USER_10, configurationB);
         Intent intent = new Intent();
         intent.setData(Uri.parse("package:" + PROVIDER_A_PACKAGE_NAME));
         broadcastReceiverArgumentCaptor.getValue().onReceive(mMockContext, intent);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index e4f9eaf..3f16a98 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -28,7 +28,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 
-import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManagerInternal;
@@ -98,7 +96,6 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Objects;
 import java.util.function.Consumer;
 
 
@@ -361,7 +358,6 @@
             throws Exception {
         mGameServiceProviderInstance.start();
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -383,7 +379,6 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
@@ -398,7 +393,6 @@
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -408,7 +402,6 @@
     public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -426,9 +419,7 @@
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
@@ -442,7 +433,6 @@
     public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -462,7 +452,6 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -488,7 +477,6 @@
         startTask(11, GAME_A_MAIN_ACTIVITY);
         startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
         mFakeGameService.requestCreateGameSession(11);
 
@@ -522,7 +510,6 @@
         startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
         startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -551,7 +538,6 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -583,7 +569,6 @@
         startTask(11, GAME_A_MAIN_ACTIVITY);
         startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
 
         mFakeGameService.requestCreateGameSession(10);
         mFakeGameService.requestCreateGameSession(11);
@@ -624,7 +609,6 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
 
         // No game session should be created.
         assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -636,7 +620,6 @@
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -677,7 +660,6 @@
     public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -699,7 +681,6 @@
     public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -721,7 +702,6 @@
     public void gameTaskFocused_propagatedToGameSession() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -747,7 +727,6 @@
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -764,7 +743,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         dispatchTaskRemoved(10);
@@ -782,7 +760,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -800,7 +777,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -830,7 +806,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -851,7 +826,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -879,7 +853,6 @@
         startTask(10, GAME_A_MAIN_ACTIVITY);
         startTask(11, GAME_A_MAIN_ACTIVITY);
 
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -897,7 +870,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -925,7 +897,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -955,7 +926,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -989,19 +959,10 @@
     }
 
     @Test
-    public void createGameSession_failurePermissionDenied() throws Exception {
-        mGameServiceProviderInstance.start();
-        startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
-        assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10));
-    }
-
-    @Test
     public void gameSessionServiceDies_severalActiveGameSessions_destroysGameSessions() {
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1030,7 +991,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1058,7 +1018,6 @@
     public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1093,7 +1052,6 @@
                 any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
         mGameServiceProviderInstance.start();
         startTask(taskId, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(taskId);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1113,7 +1071,6 @@
 
     @Test
     public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
@@ -1122,7 +1079,6 @@
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1148,11 +1104,9 @@
 
     @Test
     public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mGameServiceProviderInstance.start();
 
         startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
         FakeGameSession gameSession10 = new FakeGameSession();
@@ -1170,21 +1124,6 @@
                 .restartTaskActivityProcessIfVisible(anyInt(), anyString());
     }
 
-    @Test
-    public void restartGame_failurePermissionDenied() throws Exception {
-        mGameServiceProviderInstance.start();
-        startTask(10, GAME_A_MAIN_ACTIVITY);
-        mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
-        mFakeGameService.requestCreateGameSession(10);
-        IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement(
-                mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController;
-        mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
-        assertThrows(SecurityException.class,
-                () -> gameSessionController.restartGame(10));
-        verify(mActivityTaskManagerInternal, never())
-                .restartTaskActivityProcessIfVisible(anyInt(), anyString());
-    }
-
     private void startTask(int taskId, ComponentName componentName) {
         addRunningTaskInfo(taskId, componentName);
 
@@ -1261,14 +1200,6 @@
         }
     }
 
-    private void mockPermissionGranted(String permission) {
-        mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
-    }
-
-    private void mockPermissionDenied(String permission) {
-        mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED);
-    }
-
     private void dispatchTaskSystemBarsEvent(
             ThrowingConsumer<TaskSystemBarsListener> taskSystemBarsListenerConsumer) {
         for (TaskSystemBarsListener listener : mTaskSystemBarsListeners) {
@@ -1409,42 +1340,12 @@
     }
 
     private final class MockContext extends ContextWrapper {
-        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
-        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
-
         MockContext(Context base) {
             super(base);
         }
-
-        /**
-         * Mock checks for the specified permission, and have them behave as per {@code granted}.
-         *
-         * <p>Passing null reverts to default behavior, which does a real permission check on the
-         * test package.
-         *
-         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
-         *                {@link PackageManager#PERMISSION_DENIED}.
-         */
-        public void setPermission(String permission, Integer granted) {
-            mMockedPermissions.put(permission, granted);
-        }
-
         @Override
         public PackageManager getPackageManager() {
             return mMockPackageManager;
         }
-
-        @Override
-        public void enforceCallingPermission(String permission, @Nullable String message) {
-            final Integer granted = mMockedPermissions.get(permission);
-            if (granted == null) {
-                super.enforceCallingOrSelfPermission(permission, message);
-                return;
-            }
-
-            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
-                throw new SecurityException("[Test] permission denied: " + permission);
-            }
-        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 60ddeeb..7755552 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -149,12 +149,6 @@
                 .thenReturn(mockArray);
         when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
                 .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(mockArray);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
new file mode 100644
index 0000000..10f0a5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class ALSProbeTest {
+
+    private static final long TIMEOUT_MS = 1000;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private SensorManager mSensorManager;
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    private TestableLooper mLooper;
+    private Sensor mLightSensor = new Sensor(
+            new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0,
+                    "", "", 0, 0, 0));
+
+    private ALSProbe mProbe;
+
+    @Before
+    public void setup() {
+        mLooper = TestableLooper.get(this);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
+        mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+        reset(mSensorManager);
+    }
+
+    @Test
+    public void testEnable() {
+        final float value = 2.0f;
+        mProbe.enable();
+        verify(mSensorManager).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 2, new float[]{value}));
+
+        assertThat(mProbe.getCurrentLux()).isEqualTo(value);
+    }
+
+    @Test
+    public void testEnableOnlyOnce() {
+        mProbe.enable();
+        mProbe.enable();
+
+        verify(mSensorManager).registerListener(any(), any(), anyInt());
+        verifyNoMoreInteractions(mSensorManager);
+    }
+
+    @Test
+    public void testDisable() {
+        mProbe.enable();
+        verify(mSensorManager).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+        mProbe.disable();
+
+        verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+        verifyNoMoreInteractions(mSensorManager);
+    }
+
+    @Test
+    public void testDestroy() {
+        mProbe.destroy();
+        mProbe.enable();
+
+        verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
+        verifyNoMoreInteractions(mSensorManager);
+    }
+
+    @Test
+    public void testDisabledReportsNegativeValue() {
+        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+
+        mProbe.enable();
+        verify(mSensorManager).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
+        mProbe.disable();
+
+        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+    }
+
+    @Test
+    public void testWatchDog() {
+        mProbe.enable();
+        verify(mSensorManager).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
+        moveTimeBy(TIMEOUT_MS);
+
+        verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+        verifyNoMoreInteractions(mSensorManager);
+        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+    }
+
+    @Test
+    public void testEnableExtendsWatchDog() {
+        mProbe.enable();
+        verify(mSensorManager).registerListener(any(), any(), anyInt());
+
+        moveTimeBy(TIMEOUT_MS / 2);
+        verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
+
+        mProbe.enable();
+        moveTimeBy(TIMEOUT_MS);
+
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+        verifyNoMoreInteractions(mSensorManager);
+        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+    }
+
+    private void moveTimeBy(long millis) {
+        mLooper.moveTimeForward(millis);
+        mLooper.processAllMessages();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index e6acc90..dd7aeb7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -40,8 +40,6 @@
 import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBarService;
 
-import com.google.common.collect.ImmutableList;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -89,33 +87,64 @@
 
     @Test
     public void testIsAod() throws RemoteException {
-        mListener.onDozeChanged(true);
+        mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
         assertThat(mProvider.isAod()).isTrue();
-        mListener.onDozeChanged(false);
+        mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
         assertThat(mProvider.isAod()).isFalse();
 
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
-        mListener.onDozeChanged(true);
+        mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
         assertThat(mProvider.isAod()).isFalse();
-        mListener.onDozeChanged(false);
+        mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
         assertThat(mProvider.isAod()).isFalse();
     }
 
     @Test
+    public void testIsAwake() throws RemoteException {
+        mListener.onDozeChanged(false /* isDozing */, true /* isAwake */);
+        assertThat(mProvider.isAwake()).isTrue();
+        mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
+        assertThat(mProvider.isAwake()).isFalse();
+        mListener.onDozeChanged(true /* isDozing */, true /* isAwake */);
+        assertThat(mProvider.isAwake()).isTrue();
+        mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
+        assertThat(mProvider.isAwake()).isFalse();
+    }
+
+    @Test
     public void testSubscribesToAod() throws RemoteException {
-        final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
         final List<Boolean> actual = new ArrayList<>();
 
         mProvider.subscribe(mOpContext, ctx -> {
             assertThat(ctx).isSameInstanceAs(mOpContext);
+            assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
+            assertThat(mProvider.isAwake()).isFalse();
             actual.add(ctx.isAod);
         });
 
-        for (boolean v : expected) {
-            mListener.onDozeChanged(v);
+        for (boolean v : List.of(true, false, true, true, false, false)) {
+            mListener.onDozeChanged(v /* isDozing */, false /* isAwake */);
         }
 
-        assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+        assertThat(actual).containsExactly(true, false, true, false).inOrder();
+    }
+
+    @Test
+    public void testSubscribesToAwake() throws RemoteException {
+        final List<Boolean> actual = new ArrayList<>();
+
+        mProvider.subscribe(mOpContext, ctx -> {
+            assertThat(ctx).isSameInstanceAs(mOpContext);
+            assertThat(ctx.isAod).isFalse();
+            assertThat(mProvider.isAod()).isFalse();
+            actual.add(mProvider.isAwake());
+        });
+
+        for (boolean v : List.of(true, false, true, true, false, false)) {
+            mListener.onDozeChanged(false /* isDozing */, v /* isAwake */);
+        }
+
+        assertThat(actual).containsExactly(true, false, true, false).inOrder();
     }
 
     @Test
@@ -124,13 +153,13 @@
         mProvider.subscribe(mOpContext, emptyConsumer);
         mProvider.unsubscribe(mOpContext);
 
-        mListener.onDozeChanged(true);
+        mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
 
         final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
         mProvider.subscribe(mOpContext, nonEmptyConsumer);
-        mListener.onDozeChanged(false);
+        mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
         mProvider.unsubscribe(mOpContext);
-        mListener.onDozeChanged(true);
+        mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
 
         verify(emptyConsumer, never()).accept(any());
         verify(nonEmptyConsumer).accept(same(mOpContext));
@@ -171,7 +200,7 @@
 
     @Test
     public void testUpdate() throws RemoteException {
-        mListener.onDozeChanged(false);
+        mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
         OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
 
         // default state when nothing has been set
@@ -186,7 +215,7 @@
             final int id = 40 + type;
             final boolean aod = (type & 1) == 0;
 
-            mListener.onDozeChanged(aod);
+            mListener.onDozeChanged(aod /* isDozing */, false /* isAwake */);
             mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
             context = mProvider.updateContext(mOpContext, false /* crypto */);
             assertThat(context).isSameInstanceAs(mOpContext);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 0b8e8ad..60dc2eb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -232,7 +232,7 @@
     public void testALSCallback() {
         mLogger = createLogger();
         final CallbackWithProbe<Probe> callback =
-                mLogger.createALSCallback(true /* startWithClient */);
+                mLogger.getAmbientLightProbe(true /* startWithClient */);
 
         callback.onClientStarted(mClient);
         verify(mSensorManager).registerListener(any(), any(), anyInt());
@@ -242,10 +242,37 @@
     }
 
     @Test
+    public void testALSCallbackWhenLogsDisabled() {
+        mLogger = createLogger();
+        mLogger.disableMetrics();
+        final CallbackWithProbe<Probe> callback =
+                mLogger.getAmbientLightProbe(true /* startWithClient */);
+
+        callback.onClientStarted(mClient);
+        verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
+
+        callback.onClientFinished(mClient, true /* success */);
+        verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
+    }
+
+    @Test
+    public void testALSCallbackWhenDisabledAfterStarting() {
+        mLogger = createLogger();
+        final CallbackWithProbe<Probe> callback =
+                mLogger.getAmbientLightProbe(true /* startWithClient */);
+
+        callback.onClientStarted(mClient);
+        verify(mSensorManager).registerListener(any(), any(), anyInt());
+
+        mLogger.disableMetrics();
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+    }
+
+    @Test
     public void testALSCallbackDoesNotStart() {
         mLogger = createLogger();
         final CallbackWithProbe<Probe> callback =
-                mLogger.createALSCallback(false /* startWithClient */);
+                mLogger.getAmbientLightProbe(false /* startWithClient */);
 
         callback.onClientStarted(mClient);
         callback.onClientFinished(mClient, true /* success */);
@@ -256,7 +283,7 @@
     public void testALSCallbackDestroyed() {
         mLogger = createLogger();
         final CallbackWithProbe<Probe> callback =
-                mLogger.createALSCallback(true /* startWithClient */);
+                mLogger.getAmbientLightProbe(true /* startWithClient */);
 
         callback.onClientStarted(mClient);
         callback.onClientFinished(mClient, false /* success */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 45e3b43..eb131419 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -91,8 +91,7 @@
         mToken = new Binder();
         mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
                 BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
-                mBiometricService, LOG_NUM_RECENT_OPERATIONS,
-                CoexCoordinator.getInstance());
+                mBiometricService, LOG_NUM_RECENT_OPERATIONS);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
deleted file mode 100644
index abf992b..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FACE;
-import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FP_OTHER;
-import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.biometrics.BiometricConstants;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.LinkedList;
-
-@Presubmit
-@SmallTest
-public class CoexCoordinatorTest {
-
-    @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
-
-    @Mock
-    private CoexCoordinator.Callback mCallback;
-    @Mock
-    private CoexCoordinator.ErrorCallback mErrorCallback;
-    @Mock
-    private AuthenticationClient mFaceClient;
-    @Mock
-    private AuthenticationClient mFingerprintClient;
-    @Mock(extraInterfaces = {Udfps.class})
-    private AuthenticationClient mUdfpsClient;
-
-    private CoexCoordinator mCoexCoordinator;
-
-    @Before
-    public void setUp() {
-        mCoexCoordinator = CoexCoordinator.getInstance();
-        mCoexCoordinator.setAdvancedLogicEnabled(true);
-        mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
-        mCoexCoordinator.reset();
-    }
-
-    @Test
-    public void testBiometricPrompt_authSuccess() {
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mFaceClient, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testBiometricPrompt_authReject_whenNotLockedOut() {
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testBiometricPrompt_authReject_whenLockedOut() {
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testBiometricPrompt_coex_success() {
-        testBiometricPrompt_coex_success(false /* twice */);
-    }
-
-    @Test
-    public void testBiometricPrompt_coex_successWithoutDouble() {
-        testBiometricPrompt_coex_success(true /* twice */);
-    }
-
-    private void testBiometricPrompt_coex_success(boolean twice) {
-        initFaceAndFingerprintForBiometricPrompt();
-        when(mFaceClient.wasAuthSuccessful()).thenReturn(true);
-        when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mFaceClient, mCallback);
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mUdfpsClient, mCallback);
-
-        if (twice) {
-            verify(mCallback, never()).sendHapticFeedback();
-        } else {
-            verify(mCallback).sendHapticFeedback();
-        }
-    }
-
-    @Test
-    public void testBiometricPrompt_coex_reject() {
-        initFaceAndFingerprintForBiometricPrompt();
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
-
-        verify(mCallback, never()).sendHapticFeedback();
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                    mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
-
-        verify(mCallback).sendHapticFeedback();
-    }
-
-    @Test
-    public void testBiometricPrompt_coex_errorNoHaptics() {
-        initFaceAndFingerprintForBiometricPrompt();
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationError(mFaceClient,
-                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
-        mCoexCoordinator.onAuthenticationError(mUdfpsClient,
-                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
-
-        verify(mErrorCallback, never()).sendHapticFeedback();
-    }
-
-    private void initFaceAndFingerprintForBiometricPrompt() {
-        when(mFaceClient.isKeyguard()).thenReturn(false);
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
-        when(mUdfpsClient.isKeyguard()).thenReturn(false);
-        when(mUdfpsClient.isBiometricPrompt()).thenReturn(true);
-        when(mUdfpsClient.wasAuthAttempted()).thenReturn(true);
-    }
-
-    @Test
-    public void testKeyguard_faceAuthOnly_success() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mFaceClient, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mFaceClient, mCallback);
-        // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
-        // of removed) to keep this context.
-        // verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testKeyguard_faceAuthSuccess_nonBypass_udfpsRunning_noHaptics() {
-        testKeyguard_bypass_haptics(false /* bypassEnabled */,
-                true /* faceAccepted */,
-                false /* shouldReceiveHaptics */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuthReject_nonBypass_udfpsRunning_noHaptics() {
-        testKeyguard_bypass_haptics(false /* bypassEnabled */,
-                false /* faceAccepted */,
-                false /* shouldReceiveHaptics */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuthSuccess_bypass_udfpsRunning_haptics() {
-        testKeyguard_bypass_haptics(true /* bypassEnabled */,
-                true /* faceAccepted */,
-                true /* shouldReceiveHaptics */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuthReject_bypass_udfpsRunning_haptics() {
-        testKeyguard_bypass_haptics(true /* bypassEnabled */,
-                false /* faceAccepted */,
-                true /* shouldReceiveHaptics */);
-    }
-
-    private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
-            boolean shouldReceiveHaptics) {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        if (faceAccepted) {
-            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
-                    mCallback);
-        } else {
-            mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
-                    LockoutTracker.LOCKOUT_NONE, mCallback);
-        }
-
-        if (shouldReceiveHaptics) {
-            verify(mCallback).sendHapticFeedback();
-        } else {
-            verify(mCallback, never()).sendHapticFeedback();
-        }
-
-        verify(mCallback).sendAuthenticationResult(eq(faceAccepted) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() {
-        testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */,
-                0 /* udfpsRejectedAfterMs */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedAfterBounds() {
-        testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */,
-                CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS + 1 /* udfpsRejectedAfterMs */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsAccepted() {
-        testKeyguard_faceAuth_udfpsTouching_faceSuccess(true /* thenUdfpsAccepted */,
-                0 /* udfpsRejectedAfterMs */);
-    }
-
-    private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted,
-            long udfpsRejectedAfterMs) {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
-        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        // For easier reading
-        final CoexCoordinator.Callback faceCallback = mCallback;
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
-                faceCallback);
-        verify(faceCallback, never()).sendHapticFeedback();
-        verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
-        // CoexCoordinator requests the system to hold onto this AuthenticationClient until
-        // UDFPS result is known
-        verify(faceCallback, never()).handleLifecycleAfterAuth();
-
-        // Reset the mock
-        CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
-        assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size());
-        assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
-        if (thenUdfpsAccepted) {
-            mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
-                    udfpsCallback);
-            verify(udfpsCallback).sendHapticFeedback();
-            verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
-            verify(udfpsCallback).handleLifecycleAfterAuth();
-
-            verify(faceCallback).sendAuthenticationCanceled();
-
-            assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
-        } else {
-            mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient,
-                    LockoutTracker.LOCKOUT_NONE, udfpsCallback);
-            if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
-                verify(udfpsCallback, never()).sendHapticFeedback();
-
-                verify(faceCallback).sendHapticFeedback();
-                verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
-                verify(faceCallback).handleLifecycleAfterAuth();
-
-                assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
-            } else {
-                assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
-
-                verify(faceCallback, never()).sendHapticFeedback();
-                verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
-
-                verify(udfpsCallback).sendHapticFeedback();
-                verify(udfpsCallback)
-                        .sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-                verify(udfpsCallback).handleLifecycleAfterAuth();
-            }
-        }
-    }
-
-    @Test
-    public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
-                mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(true));
-        verify(mFaceClient).cancel();
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
-                LockoutTracker.LOCKOUT_NONE, mCallback);
-        verify(mCallback, never()).sendHapticFeedback();
-        verify(mCallback).handleLifecycleAfterAuth();
-
-        // BiometricScheduler removes the face authentication client after rejection
-        mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        // Then UDFPS rejected
-        CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
-        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient,
-                LockoutTracker.LOCKOUT_NONE, udfpsCallback);
-        verify(udfpsCallback).sendHapticFeedback();
-        verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-        verify(mCallback, never()).sendHapticFeedback();
-    }
-
-    @Test
-    public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
-        when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
-        // Auth was attempted
-        when(mUdfpsClient.getState())
-                .thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).handleLifecycleAfterAuth();
-
-        // Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since
-        // unlike face, its lifecycle becomes "paused" instead of "finished".
-        CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class);
-        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mFaceClient,
-                LockoutTracker.LOCKOUT_NONE, faceCallback);
-        verify(faceCallback).sendHapticFeedback();
-        verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-        verify(mCallback).sendHapticFeedback();
-    }
-
-    @Test
-    public void testKeyguard_capacitiveAccepted_whenFaceScanning() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mFingerprintClient.isKeyguard()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
-
-        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
-                mFingerprintClient, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testKeyguard_capacitiveRejected_whenFaceScanning() {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-        when(mFingerprintClient.isKeyguard()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
-
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
-                mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback);
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testNonKeyguard_rejectAndNotLockedOut() {
-        when(mFaceClient.isKeyguard()).thenReturn(false);
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
-                LockoutTracker.LOCKOUT_NONE, mCallback);
-
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback).sendAuthenticationResult(eq(false));
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testNonKeyguard_rejectLockedOut() {
-        when(mFaceClient.isKeyguard()).thenReturn(false);
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
-                LockoutTracker.LOCKOUT_TIMED, mCallback);
-
-        verify(mCallback).sendHapticFeedback();
-        verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
-        verify(mCallback).handleLifecycleAfterAuth();
-    }
-
-    @Test
-    public void testCleanupRunnable() {
-        LinkedList<CoexCoordinator.SuccessfulAuth> successfulAuths = mock(LinkedList.class);
-        CoexCoordinator.SuccessfulAuth auth = mock(CoexCoordinator.SuccessfulAuth.class);
-        CoexCoordinator.Callback callback = mock(CoexCoordinator.Callback.class);
-        CoexCoordinator.SuccessfulAuth.CleanupRunnable runnable =
-                new CoexCoordinator.SuccessfulAuth.CleanupRunnable(successfulAuths, auth, callback);
-        runnable.run();
-
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
-        verify(callback).handleLifecycleAfterAuth();
-        verify(successfulAuths).remove(eq(auth));
-    }
-
-    @Test
-    public void testBiometricPrompt_FaceError() {
-        when(mFaceClient.isBiometricPrompt()).thenReturn(true);
-        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationError(mFaceClient,
-                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
-        verify(mErrorCallback).sendHapticFeedback();
-    }
-
-    @Test
-    public void testKeyguard_faceAuthOnly_errorWhenBypassEnabled() {
-        testKeyguard_faceAuthOnly(true /* bypassEnabled */);
-    }
-
-    @Test
-    public void testKeyguard_faceAuthOnly_errorWhenBypassDisabled() {
-        testKeyguard_faceAuthOnly(false /* bypassEnabled */);
-    }
-
-    private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
-        when(mFaceClient.wasUserDetected()).thenReturn(true);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-
-        mCoexCoordinator.onAuthenticationError(mFaceClient,
-                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
-        verify(mErrorCallback).sendHapticFeedback();
-    }
-
-    @Test
-    public void testKeyguard_coex_faceErrorWhenBypassEnabled() {
-        testKeyguard_coex_faceError(true /* bypassEnabled */);
-    }
-
-    @Test
-    public void testKeyguard_coex_faceErrorWhenBypassDisabled() {
-        testKeyguard_coex_faceError(false /* bypassEnabled */);
-    }
-
-    private void testKeyguard_coex_faceError(boolean bypassEnabled) {
-        when(mFaceClient.isKeyguard()).thenReturn(true);
-        when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-        when(mFaceClient.wasAuthAttempted()).thenReturn(true);
-        when(mFaceClient.wasUserDetected()).thenReturn(true);
-        when(mUdfpsClient.isKeyguard()).thenReturn(true);
-        when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
-
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
-        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
-
-        mCoexCoordinator.onAuthenticationError(mFaceClient,
-                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
-
-        if (bypassEnabled) {
-            verify(mErrorCallback).sendHapticFeedback();
-        } else {
-            verify(mErrorCallback, never()).sendHapticFeedback();
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 0df3028..0815fe5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -118,8 +118,7 @@
                                 TEST_SENSOR_ID,  mBiometricLogger, mBiometricContext,
                                 mUserStartedCallback, mStartOperationsFinish);
                     }
-                },
-                CoexCoordinator.getInstance());
+                });
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index b60324e..518946a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -35,7 +35,6 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -91,8 +90,7 @@
                 null /* gestureAvailabilityDispatcher */,
                 mBiometricService,
                 () -> USER_ID,
-                mUserSwitchCallback,
-                CoexCoordinator.getInstance());
+                mUserSwitchCallback);
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index a149f3e..dea4d4f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -26,8 +26,8 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -96,6 +96,7 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
+
     @Mock
     private ISession mHal;
     @Mock
@@ -137,7 +138,7 @@
     @Before
     public void setup() {
         mContext.addMockSystemService(BiometricManager.class, mBiometricManager);
-        when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+        when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i ->
                 new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
         when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
                 i -> i.getArgument(0));
@@ -213,21 +214,41 @@
     }
 
     @Test
-    public void luxProbeWhenFingerDown() throws RemoteException {
+    public void luxProbeWhenAwake() throws RemoteException {
+        when(mBiometricContext.isAwake()).thenReturn(false, true, false);
+        when(mBiometricContext.isAod()).thenReturn(false);
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
 
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
-        verify(mLuxProbe).enable();
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
 
-        client.onAcquired(2, 0);
+        mContextInjector.getValue().accept(opContext);
+        verify(mLuxProbe, never()).enable();
+
+        reset(mLuxProbe);
+        mContextInjector.getValue().accept(opContext);
+        verify(mLuxProbe).enable();
         verify(mLuxProbe, never()).disable();
 
-        client.onPointerUp();
+        mContextInjector.getValue().accept(opContext);
         verify(mLuxProbe).disable();
+    }
 
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
-        verify(mLuxProbe, times(2)).enable();
+    @Test
+    public void luxProbeDisabledOnAod() throws RemoteException {
+        when(mBiometricContext.isAwake()).thenReturn(false);
+        when(mBiometricContext.isAod()).thenReturn(true);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+        OperationContext opContext = mOperationContextCaptor.getValue();
+        verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+
+        mContextInjector.getValue().accept(opContext);
+        verify(mLuxProbe, never()).enable();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index f77eb0b..92e1f27a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -64,6 +64,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -119,7 +120,7 @@
 
     @Before
     public void setup() {
-        when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+        when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i ->
                 new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
         when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
                 i -> i.getArgument(0));
@@ -196,21 +197,22 @@
     }
 
     @Test
-    public void luxProbeWhenFingerDown() throws RemoteException {
+    public void luxProbeWhenStarted() throws RemoteException {
         final FingerprintEnrollClient client = createClient();
         client.start(mCallback);
 
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
         verify(mLuxProbe).enable();
 
         client.onAcquired(2, 0);
-        verify(mLuxProbe, never()).disable();
-
         client.onPointerUp();
-        verify(mLuxProbe).disable();
-
         client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
-        verify(mLuxProbe, times(2)).enable();
+        verify(mLuxProbe, never()).disable();
+        verify(mLuxProbe, never()).destroy();
+
+        client.onEnrollResult(new Fingerprint("f", 30 /* fingerId */, 14 /* deviceId */),
+                0 /* remaining */);
+
+        verify(mLuxProbe).destroy();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index e1a4a2d..ff636c8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -35,7 +35,6 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.CoexCoordinator;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -91,8 +90,7 @@
                 null /* gestureAvailabilityDispatcher */,
                 mBiometricService,
                 () -> USER_ID,
-                mUserSwitchCallback,
-                CoexCoordinator.getInstance());
+                mUserSwitchCallback);
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 261b882..03ea613 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,19 +19,16 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,16 +52,22 @@
     private Resources mResources;
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
         mockDeviceConfigs();
+        try {
+            Path tempFile = Files.createTempFile("display_config", ".tmp");
+            Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+            mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+            mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+        } catch (IOException e) {
+            throw new IOException("Failed to setup the display device config.", e);
+        }
     }
 
     @Test
-    public void testConfigValuesFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
+    public void testConfigValues() {
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
@@ -85,24 +88,10 @@
         assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{50.0f, 80.0f}, 0.0f);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.0f, 75.0f}, 0.0f);
+
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testConfigValuesFromDeviceConfig() {
-        setupDisplayDeviceConfigFromDeviceConfigFile();
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 110.0f, 500.0f}, 0.0f);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{2.0f, 200.0f, 600.0f}, 0.0f);
-        // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-
+        // Also add test for the case where optional display configs are null
     }
 
     private String getContent() {
@@ -125,16 +114,6 @@
                 +   "<autoBrightness>\n"
                 +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
                 +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
-                +       "<displayBrightnessMapping>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>50</lux>\n"
-                +                "<nits>45</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>80</lux>\n"
-                +                "<nits>75</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +       "</displayBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +   "<highBrightnessMode enabled=\"true\">\n"
                 +       "<transitionPoint>0.62</transitionPoint>\n"
@@ -206,64 +185,4 @@
         when(mResources.getFloat(com.android.internal.R.dimen
                 .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
     }
-
-    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
-        Path tempFile = Files.createTempFile("display_config", ".tmp");
-        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
-        mDisplayDeviceConfig.initFromFile(tempFile.toFile());
-    }
-
-    private void setupDisplayDeviceConfigFromDeviceConfigFile() {
-        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessNits))
-                .thenReturn(screenBrightnessNits);
-        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
-                float[]{0.0f, 120.0f, 255.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessBacklight))
-                .thenReturn(screenBrightnessBacklight);
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
-
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
-
-        TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
-                float[]{2.0f, 200.0f, 600.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(screenBrightnessLevelNits);
-        TypedArray screenBrightnessLevelLux = createFloatTypedArray(new
-                float[]{0.0f, 110.0f, 500.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(screenBrightnessLevelLux);
-
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
-
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4550b56..2ccdcaa 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,6 +1672,36 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_claimedSystemOwner() {
+        // Make sure anything that claims to have a "system" owner but not actually part of the
+        // system package still gets limited on number of rules
+        for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+            ScheduleInfo si = new ScheduleInfo();
+            si.startHour = i;
+            AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+                    new ComponentName("android", "ScheduleConditionProvider" + i),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(si),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            assertNotNull(id);
+        }
+        try {
+            AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                    new ComponentName("android", "ScheduleConditionProviderFinal"),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            fail("allowed too many rules to be created");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+    }
+
+    @Test
     public void testAddAutomaticZenRule_CA() {
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
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 4449483..25c8f14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2869,6 +2869,7 @@
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
         fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
         task.addChild(taskFragment1, POSITION_TOP);
+        assertEquals(task, activity1.mStartingData.mAssociatedTask);
 
         final TaskFragment taskFragment2 = new TaskFragment(
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2890,7 +2891,6 @@
                 eq(task.mSurfaceControl));
         assertEquals(activity1.mStartingData, startingWindow.mStartingData);
         assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
-        assertEquals(task, activity1.mStartingData.mAssociatedTask);
         assertEquals(taskFragment1.getBounds(), activity1.getBounds());
         // The activity was resized by task fragment, but starting window must still cover the task.
         assertEquals(taskBounds, activity1.mStartingWindow.getBounds());
@@ -2898,7 +2898,6 @@
         // The starting window is only removed when all embedded activities are drawn.
         final WindowState activityWindow = mock(WindowState.class);
         activity1.onFirstWindowDrawn(activityWindow);
-        assertNotNull(activity1.mStartingWindow);
         activity2.onFirstWindowDrawn(activityWindow);
         assertNull(activity1.mStartingWindow);
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9fcf44e..ccec67e 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -15,6 +15,7 @@
 package android.telecom;
 
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -2417,6 +2418,10 @@
         if (service != null) {
             try {
                 result = service.createManageBlockedNumbersIntent(mContext.getPackageName());
+                if (result != null) {
+                    result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+                            mContext.getAttributionSource());
+                }
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#createManageBlockedNumbersIntent", e);
             }
@@ -2438,7 +2443,12 @@
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
-                return service.createLaunchEmergencyDialerIntent(number);
+                Intent result = service.createLaunchEmergencyDialerIntent(number);
+                if (result != null) {
+                    result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+                            mContext.getAttributionSource());
+                }
+                return result;
             } catch (RemoteException e) {
                 Log.e(TAG, "Error createLaunchEmergencyDialerIntent", e);
             }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index 3853af2..1a40f82 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -164,7 +164,6 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 1,
                             supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
                             supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 9cc1bfe..ec2b4fa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -95,7 +95,7 @@
         @JvmStatic
         fun getParams(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 58a8011..55d4129 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -103,7 +103,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index f6f3f58..725c10a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -113,8 +113,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    // b/190352379 (IME doesn't show on app launch in 90 degrees)
+                                        // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 52f561e..8832686 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -123,7 +123,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3,
+                .getConfigNonRotationTests(
                     // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index c6e25d3..71e0aa1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -141,7 +141,6 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 2,
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 23bd220..0f91fd5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -121,7 +121,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 8ce1840..007a4f1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -134,8 +134,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_0),
+                                        supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index a04a50f..216e0eda 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -127,14 +127,13 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(
-                            repetitions = 3,
-                            supportedRotations = listOf(Surface.ROTATION_0),
-                            supportedNavigationModes = listOf(
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                            )
+                .getConfigNonRotationTests(
+                    supportedRotations = listOf(Surface.ROTATION_0),
+                    supportedNavigationModes = listOf(
+                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     )
+                )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 04e4bc9..868290e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -141,8 +141,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_0),
+                                        supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index b10aed3..16c23b9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -82,8 +82,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_0),
+                                        supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index d900815..e587492 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -117,8 +117,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_90),
+                                        supportedRotations = listOf(Surface.ROTATION_90),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index fdc2193..c1f17f3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -95,8 +95,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_0),
+                                        supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 9475734..5fd9442 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -265,7 +265,6 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 1,
                     supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
                     supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 2e22e62..0281a60 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -192,8 +192,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedRotations = listOf(Surface.ROTATION_0)
+                                        supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 4f47ec4..85bf6d7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -200,8 +200,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedNavigationModes = listOf(
+                                        supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     ),
                     supportedRotations = listOf(Surface.ROTATION_0)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 33c280e..eb9acc4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -142,7 +142,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index bfc7b39..b3db5b70 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -171,7 +171,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(repetitions = 3)
+                    .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index f93d7a0..8c1d244 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -221,7 +221,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(repetitions = 3)
+                    .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index 75311ea..caf2e2d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -127,7 +127,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                    .getConfigNonRotationTests(repetitions = 3)
+                    .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index dbe5418..e744d44 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -157,7 +157,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 915b702..4ea4243 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -284,7 +284,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 7c07ace..a3dd0cb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -130,7 +130,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 53be7d4..82e30ac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -233,8 +233,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedNavigationModes =
+                                        supportedNavigationModes =
                     listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
                     supportedRotations = listOf(Surface.ROTATION_0)
                 )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index fe5e74b..5f342a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -263,7 +263,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 3)
+                .getConfigNonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 181767b..f85bad3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -278,8 +278,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 3,
-                    supportedNavigationModes = listOf(
+                                        supportedNavigationModes = listOf(
                         WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                     ),
                     supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 0f05622..f6392ca 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -298,8 +298,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 3,
-                            supportedNavigationModes = listOf(
+                                                        supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                             ),
                             supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index d1f356c..a714111 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -324,8 +324,7 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                     .getConfigNonRotationTests(
-                            repetitions = 3,
-                            supportedNavigationModes = listOf(
+                                                        supportedNavigationModes = listOf(
                                     WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                             ),
                             // TODO: Test with 90 rotation
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 4be8963..e6c1eac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -146,7 +146,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests(repetitions = 3)
+                .getConfigRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 0912812..07c2130 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -241,7 +241,7 @@
         @JvmStatic
         private fun getConfigurations(): List<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests(repetitions = 2)
+                .getConfigRotationTests()
                 .flatMap { sourceConfig ->
                     val defaultRun = createConfig(sourceConfig, starveUiThread = false)
                     val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
index 8b69db7..dc34cb6 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -15,21 +15,25 @@
  */
 package com.google.android.test.handwritingime;
 
+import android.R;
 import android.annotation.Nullable;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
-import android.os.Bundle;
 import android.util.Log;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
-import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.DeleteGesture;
+import android.view.inputmethod.HandwritingGesture;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 import android.widget.Spinner;
 import android.widget.Toast;
 
@@ -39,19 +43,19 @@
 
     public static final int HEIGHT_DP = 100;
 
-
     private static final int OP_NONE = 0;
     private static final int OP_SELECT = 1;
     private static final int OP_DELETE = 2;
-    private static final int OP_DELETE_SPACE = 3;
-    private static final int OP_INSERT = 4;
+    private static final int OP_INSERT = 3;
 
     private Window mInkWindow;
     private InkView mInk;
 
     static final String TAG = "HandwritingIme";
     private int mRichGestureMode = OP_NONE;
+    private int mRichGestureGranularity = -1;
     private Spinner mRichGestureModeSpinner;
+    private Spinner mRichGestureGranularitySpinner;
     private PointF mRichGestureStartPoint;
 
 
@@ -86,13 +90,45 @@
         switch (event.getAction()) {
             case MotionEvent.ACTION_UP: {
                 if (areRichGesturesEnabled()) {
-                    Bundle bundle = new Bundle();
-                    bundle.putInt("operation", mRichGestureMode);
-                    bundle.putFloat("left", mRichGestureStartPoint.x);
-                    bundle.putFloat("top", mRichGestureStartPoint.y);
-                    bundle.putFloat("right", event.getX());
-                    bundle.putFloat("bottom", event.getY());
-                    performPrivateCommand("android.widget.RichGesture", bundle);
+                    HandwritingGesture gesture = null;
+                    switch(mRichGestureMode) {
+                        case OP_SELECT:
+                            SelectGesture.Builder builder = new SelectGesture.Builder();
+                            builder.setGranularity(mRichGestureGranularity)
+                                    .setSelectionArea(new RectF(mRichGestureStartPoint.x,
+                                            mRichGestureStartPoint.y, event.getX(), event.getY()))
+                                    .setFallbackText("fallback text");
+                            gesture = builder.build();
+                            break;
+                        case OP_DELETE:
+                            DeleteGesture.Builder builder1 = new DeleteGesture.Builder();
+                            builder1.setGranularity(mRichGestureGranularity)
+                                    .setDeletionArea(new RectF(mRichGestureStartPoint.x,
+                                            mRichGestureStartPoint.y, event.getX(), event.getY()))
+                                    .setFallbackText("fallback text");
+                            gesture = builder1.build();
+                            break;
+                        case OP_INSERT:
+                            InsertGesture.Builder builder2 = new InsertGesture.Builder();
+                            builder2.setInsertionPoint(
+                                    new PointF(mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+                                    .setTextToInsert(" ")
+                                    .setFallbackText("fallback text");
+                            gesture = builder2.build();
+
+                    }
+                    if (gesture == null) {
+                        // This shouldn't happen
+                        Log.e(TAG, "Unrecognized gesture mode: " + mRichGestureMode);
+                        return;
+                    }
+                    InputConnection ic = getCurrentInputConnection();
+                    if (getCurrentInputStarted() && ic != null) {
+                        ic.performHandwritingGesture(gesture, null, null);
+                    } else {
+                        // This shouldn't happen
+                        Log.e(TAG, "No active InputConnection");
+                    }
 
                     Log.d(TAG, "Sending RichGesture " + mRichGestureMode + " (Screen) Left: "
                             + mRichGestureStartPoint.x + ", Top: " + mRichGestureStartPoint.y
@@ -123,8 +159,15 @@
         view.addView(inner, new FrameLayout.LayoutParams(
                 FrameLayout.LayoutParams.MATCH_PARENT, height));
 
-        view.addView(getRichGestureActionsSpinner());
-        inner.setBackgroundColor(getColor(R.color.abc_tint_spinner));
+        LinearLayout layout = new LinearLayout(this);
+        layout.setLayoutParams(new LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        layout.setOrientation(LinearLayout.VERTICAL);
+        layout.addView(getRichGestureActionsSpinner());
+        layout.addView(getRichGestureGranularitySpinner());
+
+        view.addView(layout);
+        inner.setBackgroundColor(getColor(R.color.holo_green_light));
 
         return view;
     }
@@ -133,14 +176,12 @@
         if (mRichGestureModeSpinner != null) {
             return mRichGestureModeSpinner;
         }
-        //get the spinner from the xml.
         mRichGestureModeSpinner = new Spinner(this);
         mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
         mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
         String[] items =
                 new String[] { "Handwriting IME - Rich gesture disabled", "Rich gesture SELECT",
-                        "Rich gesture DELETE", "Rich gesture DELETE SPACE",
-                        "Rich gesture INSERT" };
+                        "Rich gesture DELETE", "Rich gesture INSERT" };
         ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                 android.R.layout.simple_spinner_dropdown_item, items);
         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@@ -149,17 +190,52 @@
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 mRichGestureMode = position;
+                mRichGestureGranularitySpinner.setEnabled(
+                        mRichGestureMode != OP_INSERT && mRichGestureMode != OP_NONE);
                 Log.d(TAG, "Setting RichGesture Mode " + mRichGestureMode);
             }
 
             @Override
             public void onNothingSelected(AdapterView<?> parent) {
                 mRichGestureMode = OP_NONE;
+                mRichGestureGranularitySpinner.setEnabled(false);
             }
         });
+        mRichGestureModeSpinner.setSelection(0); // default disabled
         return mRichGestureModeSpinner;
     }
 
+    private View getRichGestureGranularitySpinner() {
+        if (mRichGestureGranularitySpinner != null) {
+            return mRichGestureGranularitySpinner;
+        }
+        mRichGestureGranularitySpinner = new Spinner(this);
+        mRichGestureGranularitySpinner.setPadding(100, 0, 100, 0);
+        mRichGestureGranularitySpinner.setTooltipText(" Granularity");
+        String[] items =
+                new String[] { "Granularity - UNDEFINED",
+                        "Granularity - WORD", "Granularity - CHARACTER"};
+        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
+                android.R.layout.simple_spinner_dropdown_item, items);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mRichGestureGranularitySpinner.setAdapter(adapter);
+        mRichGestureGranularitySpinner.setOnItemSelectedListener(
+                new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                mRichGestureGranularity = position;
+                Log.d(TAG, "Setting RichGesture Granularity " + mRichGestureGranularity);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+                mRichGestureGranularity = 0;
+            }
+        });
+        mRichGestureGranularitySpinner.setSelection(1);
+        return mRichGestureGranularitySpinner;
+    }
+
     public void onPrepareStylusHandwriting() {
         Log.d(TAG, "onPrepareStylusHandwriting ");
         if (mInk == null) {
@@ -190,15 +266,6 @@
         return false;
     }
 
-    boolean performPrivateCommand(String action, Bundle bundle) {
-        if (!getCurrentInputStarted()) {
-            Log.e(TAG, "Input hasnt started, can't performPrivateCommand");
-            return false;
-        }
-
-        return getCurrentInputConnection().performPrivateCommand(action, bundle);
-    }
-
     private boolean areRichGesturesEnabled() {
         return mRichGestureMode != OP_NONE;
     }
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
index c9e429b..94b1f86 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -30,7 +30,7 @@
 import android.view.WindowMetrics;
 
 class InkView extends View {
-    private static final long FINISH_TIMEOUT = 600;
+    private static final long FINISH_TIMEOUT = 1500;
     private final HandwritingIme.HandwritingFinisher mHwCanceller;
     private final HandwritingIme.StylusConsumer mConsumer;
     private final int mTopInset;
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 9f6ce4e..b7c4c5b 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -61,6 +61,7 @@
     static_libs: ["RollbackTestLib", "frameworks-base-hostutils"],
     test_suites: ["general-tests"],
     test_config: "NetworkStagedRollbackTest.xml",
+    data: [":RollbackTest"],
 }
 
 java_test_host {
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 1709e15..ffde8c7 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -58,6 +58,7 @@
         ":apex.apexd_test",
         ":com.android.apex.apkrollback.test_v1",
         ":com.android.apex.apkrollback.test_v2",
+        ":StagedInstallInternalTestApp",
         ":StagedInstallTestApexV2",
         ":StagedInstallTestApexV2_WrongSha",
         ":TestAppAv1",
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 46a846b..4fb7ed1 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -369,9 +369,13 @@
 
     bool sparse_encode = use_sparse_entries_;
 
-    // Only sparse encode if the entries will be read on platforms O+.
-    sparse_encode =
-        sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
+    if (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0) {
+      // Sparse encode if sdk version is not set in context and config.
+    } else {
+      // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
+      sparse_encode = sparse_encode &&
+                      (context_->GetMinSdkVersion() >= SDK_S_V2 || config.sdkVersion >= SDK_S_V2);
+    }
 
     // Only sparse encode if the offsets are representable in 2 bytes.
     sparse_encode =
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index e48fca6..f551bf6 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -330,7 +330,7 @@
   std::unique_ptr<IAaptContext> context = test::ContextBuilder()
                                               .SetCompilationPackage("android")
                                               .SetPackageId(0x01)
-                                              .SetMinSdkVersion(SDK_O)
+                                              .SetMinSdkVersion(SDK_S_V2)
                                               .Build();
 
   const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
@@ -376,7 +376,7 @@
                                               .SetMinSdkVersion(SDK_LOLLIPOP)
                                               .Build();
 
-  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v32");
   auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
 
   TableFlattenerOptions options;
@@ -391,6 +391,46 @@
   EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
 }
 
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
+
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+  TableFlattenerOptions options;
+  options.use_sparse_entries = true;
+
+  std::string no_sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+  std::string sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+  // Attempt to parse the sparse contents.
+
+  ResourceTable sparse_table;
+  BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
+                              sparse_contents.data(), sparse_contents.size());
+  ASSERT_TRUE(parser.Parse());
+
+  auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+                                                        sparse_config);
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(0u, value->value.data);
+
+  ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+                                                       sparse_config),
+              IsNull());
+
+  value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+                                                   sparse_config);
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(4u, value->value.data);
+}
+
 TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder()
                                               .SetCompilationPackage("android")