Merge "Update TTS speech rate option with percentages"
diff --git a/Android.bp b/Android.bp
index eae0d73..30b38d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,7 +72,6 @@
         ":framework-keystore-sources",
         ":framework-identity-sources",
         ":framework-location-sources",
-        ":framework-lowpan-sources",
         ":framework-mca-effect-sources",
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
@@ -167,7 +166,6 @@
             "identity/java",
             "keystore/java",
             "location/java",
-            "lowpan/java",
             "media/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
@@ -277,7 +275,6 @@
             ":framework-keystore-sources",
             ":framework-identity-sources",
             ":framework-location-sources",
-            ":framework-lowpan-sources",
             ":framework-mca-effect-sources",
             ":framework-mca-filterfw-sources",
             ":framework-mca-filterpacks-sources",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f8aa7e9..1d92778 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,4 +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}
+
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 1e2650d..3a23b54 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -28,6 +28,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,6 +51,14 @@
     @Rule
     public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
 
+    @Before
+    public void setUp() {
+        // Parse and load the preinstalled fonts in the test process so that:
+        // (1) Updated fonts do not affect test results.
+        // (2) Lazy-loading of fonts does not affect test results (esp. testSerializeFontMap).
+        Typeface.loadPreinstalledSystemFontMap();
+    }
+
     @ManualBenchmarkState.ManualBenchmarkTest(
             warmupDurationNs = WARMUP_DURATION_NS,
             targetTestDurationNs = TARGET_TEST_DURATION_NS)
@@ -61,8 +70,12 @@
         long elapsedTime = 0;
         while (state.keepRunning(elapsedTime)) {
             long startTime = System.nanoTime();
-            Typeface.serializeFontMap(systemFontMap);
+            SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
             elapsedTime = System.nanoTime() - startTime;
+            sharedMemory.close();
+            android.util.Log.i(TAG,
+                    "testSerializeFontMap isWarmingUp=" + state.isWarmingUp()
+                            + " elapsedTime=" + elapsedTime);
         }
     }
 
diff --git a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
index 89a5219..c908d6a 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
     <application android:label="SurfaceFlingerPerfTests">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.perftests.utils.SurfaceFlingerTestActivity"
+        <activity android:name="android.surfaceflinger.SurfaceFlingerTestActivity"
                   android:exported="true">
 
             <intent-filter>
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java
new file mode 100644
index 0000000..52fb8a6
--- /dev/null
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.surfaceflinger;
+
+import android.annotation.ColorInt;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
+import android.view.SurfaceControl;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * Allocates n amount of buffers to a SurfaceControl using a Queue implementation. Executes a
+ * releaseCallback so a buffer can be safely re-used.
+ *
+ * @hide
+ */
+public class BufferFlinger {
+    ArrayBlockingQueue<GraphicBuffer> mBufferQ;
+
+    public BufferFlinger(int numOfBuffers, @ColorInt int color) {
+        mBufferQ = new ArrayBlockingQueue<>(numOfBuffers);
+
+        while (numOfBuffers > 0) {
+            GraphicBuffer buffer = GraphicBuffer.create(500, 500,
+                    PixelFormat.RGBA_8888,
+                    GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+                            | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+            Canvas canvas = buffer.lockCanvas();
+            canvas.drawColor(color);
+            buffer.unlockCanvasAndPost(canvas);
+
+            mBufferQ.add(buffer);
+            numOfBuffers--;
+        }
+    }
+
+    public void addBuffer(SurfaceControl.Transaction t, SurfaceControl surfaceControl)
+            throws InterruptedException {
+        GraphicBuffer buffer = mBufferQ.take();
+        t.setBuffer(surfaceControl,
+                HardwareBuffer.createFromGraphicBuffer(buffer),
+                null,
+                (SyncFence fence) -> {
+                    releaseCallback(fence, buffer);
+                });
+    }
+
+    public void releaseCallback(SyncFence fence, GraphicBuffer buffer) {
+        if (fence != null) {
+            fence.awaitForever();
+        }
+        mBufferQ.add(buffer);
+    }
+
+    public void freeBuffers() {
+        for (GraphicBuffer buffer : mBufferQ) {
+            buffer.destroy();
+        }
+    }
+}
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
index 8efe48d..f4d0c05 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
@@ -16,37 +16,51 @@
 
 package android.surfaceflinger;
 
+import android.graphics.Color;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.SurfaceFlingerTestActivity;
+import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class SurfaceFlingerPerfTest {
     protected ActivityScenarioRule<SurfaceFlingerTestActivity> mActivityRule =
             new ActivityScenarioRule<>(SurfaceFlingerTestActivity.class);
     protected PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private SurfaceFlingerTestActivity mActivity;
+    static final int BUFFER_COUNT = 2;
 
     @Rule
     public final RuleChain mAllRules = RuleChain
             .outerRule(mPerfStatusReporter)
             .around(mActivityRule);
-
+    @Before
+    public void setup() {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+    }
     @Test
-    public void helloWorld() throws Exception {
+    public void submitSingleBuffer() throws Exception {
+        SurfaceControl sc = mActivity.getChildSurfaceControl();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        BufferFlinger bufferflinger = new BufferFlinger(BUFFER_COUNT, Color.GREEN);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        t.show(sc);
+
         while (state.keepRunning()) {
-            // Do Something
+            bufferflinger.addBuffer(t, sc);
+            t.apply();
         }
+        bufferflinger.freeBuffers();
     }
 }
+
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
new file mode 100644
index 0000000..a9b2a31
--- /dev/null
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.surfaceflinger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class SurfaceFlingerTestActivity extends Activity {
+    public TestSurfaceView mTestSurfaceView;
+    SurfaceControl mSurfaceControl;
+    CountDownLatch mIsReady = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mTestSurfaceView = new TestSurfaceView(this);
+        setContentView(mTestSurfaceView);
+    }
+
+    public SurfaceControl getChildSurfaceControl() throws InterruptedException {
+        return mTestSurfaceView.getChildSurfaceControlHelper();
+    }
+
+    public class TestSurfaceView extends SurfaceView {
+        public TestSurfaceView(Context context) {
+            super(context);
+            SurfaceHolder holder = getHolder();
+            holder.addCallback(new SurfaceHolder.Callback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    mIsReady.countDown();
+                }
+                @Override
+                public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                        int height) {}
+                @Override
+                public void surfaceDestroyed(SurfaceHolder holder) {
+                }
+            });
+        }
+
+        public SurfaceControl getChildSurfaceControlHelper() throws InterruptedException {
+            mIsReady.await();
+            SurfaceHolder holder = getHolder();
+
+            // check to see if surface is valid
+            if (holder.getSurface().isValid()) {
+                mSurfaceControl = getSurfaceControl();
+            }
+            return new SurfaceControl.Builder()
+                    .setName("ChildSurfaceControl")
+                    .setParent(mSurfaceControl)
+                    .build();
+        }
+    }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java
deleted file mode 100644
index 511ebbe..0000000
--- a/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.perftests.utils;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.SurfaceView;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-
-/**
- * A simple activity used for testing, e.g. performance of activity switching, or as a base
- * container of testing view.
- */
-public class SurfaceFlingerTestActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-        final LinearLayout layout = new LinearLayout(this);
-        layout.setOrientation(LinearLayout.VERTICAL);
-
-        final SurfaceView surfaceview = new SurfaceView(this);
-        layout.addView(surfaceview);
-        setContentView(layout);
-
-    }
-}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c92c634..fb62920 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -153,9 +153,9 @@
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, mOutFrames,
-                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
-                        new Bundle());
+                        mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+                        mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
+                        mOutControls, new Bundle());
             }
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d8e25b6..c0a9e67 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -3078,15 +3078,15 @@
                 pw.decreaseIndent();
                 pw.println();
             } else {
-                if (mAppStateTracker != null) {
-                    mAppStateTracker.dump(pw);
-                    pw.println();
-                }
-
                 pw.println("App Standby Parole: " + mAppStandbyParole);
                 pw.println();
             }
 
+            if (mAppStateTracker != null) {
+                mAppStateTracker.dump(pw);
+                pw.println();
+            }
+
             final long nowELAPSED = mInjector.getElapsedRealtime();
             final long nowUPTIME = SystemClock.uptimeMillis();
             final long nowRTC = mInjector.getCurrentTimeMillis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 73508c8..794362b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1556,7 +1556,10 @@
 
         // Create the controllers.
         mControllers = new ArrayList<StateController>();
-        final FlexibilityController flexibilityController = new FlexibilityController(this);
+        mPrefetchController = new PrefetchController(this);
+        mControllers.add(mPrefetchController);
+        final FlexibilityController flexibilityController =
+                new FlexibilityController(this, mPrefetchController);
         mControllers.add(flexibilityController);
         final ConnectivityController connectivityController =
                 new ConnectivityController(this, flexibilityController);
@@ -1575,8 +1578,6 @@
         mControllers.add(new ContentObserverController(this));
         mDeviceIdleJobsController = new DeviceIdleJobsController(this);
         mControllers.add(mDeviceIdleJobsController);
-        mPrefetchController = new PrefetchController(this);
-        mControllers.add(mPrefetchController);
         mQuotaController =
                 new QuotaController(this, backgroundJobsController, connectivityController);
         mControllers.add(mQuotaController);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index f4ee0ae..2e41dfd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -23,6 +23,7 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -36,6 +37,7 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -44,14 +46,13 @@
 import com.android.server.utils.AlarmQueue;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Predicate;
 
 /**
  * Controller that tracks the number of flexible constraints being actively satisfied.
  * Drops constraint for TOP apps and lowers number of required constraints with time.
- *
- * TODO(b/238887951): handle prefetch
  */
 public final class FlexibilityController extends StateController {
     private static final String TAG = "JobScheduler.Flexibility";
@@ -68,24 +69,15 @@
     private static final int FLEXIBLE_CONSTRAINTS =
             JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
-    @VisibleForTesting
-    static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
+    private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
 
     static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
 
-    @VisibleForTesting
     static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
 
-    /** Hard cutoff to remove flexible constraints. */
-    private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
-
-    /**
-     * The default deadline that all flexible constraints should be dropped by if a job lacks
-     * a deadline.
-     */
-    private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
+    private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
 
     /**
      * Keeps track of what flexible constraints are satisfied at the moment.
@@ -94,30 +86,98 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     int mSatisfiedFlexibleConstraints;
+
+    /** Hard cutoff to remove flexible constraints. */
+    private long mDeadlineProximityLimitMs =
+            FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
+
+    /**
+     * The default deadline that all flexible constraints should be dropped by if a job lacks
+     * a deadline.
+     */
+    private long mFallbackFlexibilityDeadlineMs =
+            FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+
     @GuardedBy("mLock")
-    private boolean mFlexibilityEnabled = FcConstants.DEFAULT_FLEXIBILITY_ENABLED;
+    @VisibleForTesting
+    boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+
+    private long mMinTimeBetweenFlexibilityAlarmsMs =
+            FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+
+    /**
+     * The percent of a job's lifecycle to drop number of required constraints.
+     * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+     * the controller should have i+1 constraints dropped.
+     */
+    private int[] mPercentToDropConstraints;
 
     @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
-    private final FcConstants mFcConstants;
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
+    @VisibleForTesting
+    final FcConfig mFcConfig;
 
-    private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
-    private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
+    @VisibleForTesting
+    final PrefetchController mPrefetchController;
 
     /**
-     * The percent of a Jobs lifecycle to drop number of required constraints.
-     * PERCENT_TO_DROP_CONSTRAINTS[i] denotes that at x% of a Jobs lifecycle,
-     * the controller should have i+1 constraints dropped.
+     * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
+     * the last time the app was used and the last time the launch time was updated.
      */
-    private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>();
 
-    public FlexibilityController(JobSchedulerService service) {
+    @VisibleForTesting
+    final PrefetchController.PrefetchChangedListener mPrefetchChangedListener =
+            new PrefetchController.PrefetchChangedListener() {
+                @Override
+                public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
+                        String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+                    synchronized (mLock) {
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+                        final long prefetchThreshold =
+                                mPrefetchController.getLaunchTimeThresholdMs();
+                        boolean jobWasInPrefetchWindow  = prevEstimatedLaunchTime
+                                - prefetchThreshold < nowElapsed;
+                        boolean jobIsInPrefetchWindow  = newEstimatedLaunchTime
+                                - prefetchThreshold < nowElapsed;
+                        if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) {
+                            // If the job was in the window previously then changing the start
+                            // of the lifecycle to the current moment without a large change in the
+                            // end would squeeze the window too tight fail to drop constraints.
+                            mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed,
+                                    mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L)));
+                        }
+                        for (int i = 0; i < jobs.size(); i++) {
+                            JobStatus js = jobs.valueAt(i);
+                            if (!js.hasFlexibilityConstraint()) {
+                                continue;
+                            }
+                            mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+                        }
+                    }
+                }
+            };
+
+    public FlexibilityController(
+            JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
         mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
-        mFcConstants = new FcConstants();
+        mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                 mContext, JobSchedulerBackgroundThread.get().getLooper());
+        mPercentToDropConstraints =
+                mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        mPrefetchController = prefetchController;
+        if (mFlexibilityEnabled) {
+            mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
+        }
     }
 
     /**
@@ -131,7 +191,7 @@
             js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
             final long nowElapsed = sElapsedRealtimeClock.millis();
             js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
-            mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js));
+            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
         }
     }
 
@@ -144,6 +204,19 @@
         }
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onAppRemovedLocked(String packageName, int uid) {
+        final int userId = UserHandle.getUserId(uid);
+        mPrefetchLifeCycleStart.delete(userId, packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onUserRemovedLocked(int userId) {
+        mPrefetchLifeCycleStart.delete(userId);
+    }
+
     /** Checks if the flexibility constraint is actively satisfied for a given job. */
     @GuardedBy("mLock")
     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
@@ -165,6 +238,7 @@
      * Sets the controller's constraint to a given state.
      * Changes flexibility constraint satisfaction for affected jobs.
      */
+    @VisibleForTesting
     void setConstraintSatisfied(int constraint, boolean state) {
         synchronized (mLock) {
             final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -187,21 +261,20 @@
             // of satisfied system-wide constraints and iterate to the max number of potentially
             // satisfied constraints, determined by how many job-specific constraints exist.
             for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
-                final ArraySet<JobStatus> jobs = mFlexibilityTracker
+                final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                         .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
 
-                if (jobs == null) {
+                if (jobsByNumConstraints == null) {
                     // If there are no more jobs to iterate through we can just return.
                     return;
                 }
 
-                for (int i = 0; i < jobs.size(); i++) {
-                    JobStatus js = jobs.valueAt(i);
+                for (int i = 0; i < jobsByNumConstraints.size(); i++) {
+                    JobStatus js = jobsByNumConstraints.valueAt(i);
                     js.setFlexibilityConstraintSatisfied(
                             nowElapsed, isFlexibilitySatisfiedLocked(js));
                 }
             }
-
         }
     }
 
@@ -211,16 +284,85 @@
         return (mSatisfiedFlexibleConstraints & constraint) != 0;
     }
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    long getLifeCycleBeginningElapsedLocked(JobStatus js) {
+        if (js.getJob().isPrefetch()) {
+            final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime());
+            final long estimatedLaunchTime =
+                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+            long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
+                    js.getSourceUserId(), js.getSourcePackageName(), 0L);
+            if (estimatedLaunchTime != Long.MAX_VALUE) {
+                prefetchWindowStart = Math.max(prefetchWindowStart,
+                        estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs());
+            }
+            return Math.max(prefetchWindowStart, earliestRuntime);
+        }
+        return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
+                ? js.enqueueTime : js.getEarliestRunTime();
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+        if (js.getJob().isPrefetch()) {
+            final long estimatedLaunchTime =
+                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+            // Prefetch jobs aren't supposed to have deadlines after T.
+            // But some legacy apps might still schedule them with deadlines.
+            if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) {
+                // If there is a deadline, the earliest time is the end of the lifecycle.
+                return Math.min(
+                        estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+                        js.getLatestRunTimeElapsed());
+            }
+            if (estimatedLaunchTime != Long.MAX_VALUE) {
+                return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
+            }
+            // There is no deadline and no estimated launch time.
+            return NO_LIFECYCLE_END;
+        }
+        return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
+                ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    int getCurPercentOfLifecycleLocked(JobStatus js) {
+        final long earliest = getLifeCycleBeginningElapsedLocked(js);
+        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
+            return 0;
+        }
+        if (nowElapsed > latest || latest == earliest) {
+            return 100;
+        }
+        final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest));
+        return percentInTime;
+    }
+
     /** The elapsed time that marks when the next constraint should be dropped. */
     @VisibleForTesting
     @ElapsedRealtimeLong
-    long getNextConstraintDropTimeElapsed(JobStatus js) {
-        final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
-                ? js.enqueueTime : js.getEarliestRunTime();
-        final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
-                ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
-                : js.getLatestRunTimeElapsed();
-        final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
+    @GuardedBy("mLock")
+    long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
+        final long earliest = getLifeCycleBeginningElapsedLocked(js);
+        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+    }
+
+    /** The elapsed time that marks when the next constraint should be dropped. */
+    @VisibleForTesting
+    @ElapsedRealtimeLong
+    @GuardedBy("mLock")
+    long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
+        if (latest == NO_LIFECYCLE_END
+                || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
+            return NO_LIFECYCLE_END;
+        }
+        final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
         final long percentInTime = ((latest - earliest) * percent) / 100;
         return earliest + percentInTime;
     }
@@ -233,10 +375,28 @@
         }
         final long nowElapsed = sElapsedRealtimeClock.millis();
         List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
+        boolean hasPrefetch = false;
         for (int i = 0; i < jobsByUid.size(); i++) {
             JobStatus js = jobsByUid.get(i);
             if (js.hasFlexibilityConstraint()) {
                 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                hasPrefetch |= js.getJob().isPrefetch();
+            }
+        }
+
+        // Prefetch jobs can't run when the app is TOP, so it should not be included in their
+        // lifecycle, and marks the beginning of a new lifecycle.
+        if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) {
+            final int userId = UserHandle.getUserId(uid);
+            final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid);
+            if (pkgs == null) {
+                return;
+            }
+            for (int i = 0; i < pkgs.size(); i++) {
+                String pkg = pkgs.valueAt(i);
+                mPrefetchLifeCycleStart.add(userId, pkg,
+                        Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L),
+                                nowElapsed));
             }
         }
     }
@@ -244,8 +404,7 @@
     @Override
     @GuardedBy("mLock")
     public void onConstantsUpdatedLocked() {
-        if (mFcConstants.mShouldReevaluateConstraints) {
-            // Update job bookkeeping out of band.
+        if (mFcConfig.mShouldReevaluateConstraints) {
             JobSchedulerBackgroundThread.getHandler().post(() -> {
                 final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                 synchronized (mLock) {
@@ -255,6 +414,8 @@
                                 .getJobsByNumRequiredConstraints(j);
                         for (int i = 0; i < jobs.size(); i++) {
                             JobStatus js = jobs.valueAt(i);
+                            mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
                             if (js.setFlexibilityConstraintSatisfied(
                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
                                 changedJobs.add(js);
@@ -272,7 +433,13 @@
     @Override
     @GuardedBy("mLock")
     public void prepareForUpdatedConstantsLocked() {
-        mFcConstants.mShouldReevaluateConstraints = false;
+        mFcConfig.mShouldReevaluateConstraints = false;
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void processConstantLocked(DeviceConfig.Properties properties, String key) {
+        mFcConfig.processConstantLocked(properties, key);
     }
 
     @VisibleForTesting
@@ -281,7 +448,7 @@
 
         FlexibilityTracker(int numFlexibleConstraints) {
             mTrackedJobs = new ArrayList<>();
-            for (int i = 0; i <= numFlexibleConstraints; i++) {
+            for (int i = 0; i < numFlexibleConstraints; i++) {
                 mTrackedJobs.add(new ArraySet<JobStatus>());
             }
         }
@@ -312,6 +479,19 @@
             mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
         }
 
+        public void resetJobNumDroppedConstraints(JobStatus js) {
+            final int curPercent = getCurPercentOfLifecycleLocked(js);
+            int toDrop = 0;
+            final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+                    + (js.getPreferUnmetered() ? 1 : 0);
+            for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
+                if (curPercent >= mPercentToDropConstraints[i]) {
+                    toDrop++;
+                }
+            }
+            adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop);
+        }
+
         /** Returns all tracked jobs. */
         public ArrayList<ArraySet<JobStatus>> getArrayList() {
             return mTrackedJobs;
@@ -323,6 +503,9 @@
          * Jobs with 0 required flexible constraints are removed from the tracker.
          */
         public boolean adjustJobsRequiredConstraints(JobStatus js, int n) {
+            if (n == 0) {
+                return false;
+            }
             remove(js);
             js.adjustNumRequiredFlexibleConstraints(n);
             final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -342,7 +525,7 @@
         public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
             for (int i = 0; i < mTrackedJobs.size(); i++) {
                 ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
-                for (int j = 0; j < mTrackedJobs.size(); j++) {
+                for (int j = 0; j < jobs.size(); j++) {
                     final JobStatus js = jobs.valueAt(j);
                     if (!predicate.test(js)) {
                         continue;
@@ -357,11 +540,12 @@
         }
     }
 
-    private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
+    @VisibleForTesting
+    class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
         private FlexibilityAlarmQueue(Context context, Looper looper) {
             super(context, looper, "*job.flexibility_check*",
                     "Flexible Constraint Check", false,
-                    MIN_TIME_BETWEEN_ALARMS_MS);
+                    mMinTimeBetweenFlexibilityAlarmsMs);
         }
 
         @Override
@@ -369,38 +553,100 @@
             return js.getSourceUserId() == userId;
         }
 
+        public void scheduleDropNumConstraintsAlarm(JobStatus js) {
+            long nextTimeElapsed;
+            synchronized (mLock) {
+                final long earliest = getLifeCycleBeginningElapsedLocked(js);
+                final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+                nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+                if (nextTimeElapsed == NO_LIFECYCLE_END) {
+                    // There is no known or estimated next time to drop a constraint.
+                    removeAlarmForKey(js);
+                    return;
+                }
+
+                if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) {
+                    mFlexibilityTracker.adjustJobsRequiredConstraints(
+                            js, -js.getNumRequiredFlexibleConstraints());
+                    return;
+                }
+                addAlarm(js, nextTimeElapsed);
+            }
+        }
+
         @Override
         protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
             synchronized (mLock) {
-                JobStatus js;
+                ArraySet<JobStatus> changedJobs = new ArraySet<>();
                 for (int i = 0; i < expired.size(); i++) {
-                    js = expired.valueAt(i);
-                    long time = getNextConstraintDropTimeElapsed(js);
-                    int toDecrease =
-                            js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
-                            ? -js.getNumRequiredFlexibleConstraints() : -1;
-                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
-                        mFlexibilityAlarmQueue.addAlarm(js, time);
+                    JobStatus js = expired.valueAt(i);
+                    boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
+
+                    final long earliest = getLifeCycleBeginningElapsedLocked(js);
+                    final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+                    final long nowElapsed = sElapsedRealtimeClock.millis();
+
+                    if (latest - nowElapsed < mDeadlineProximityLimitMs) {
+                        mFlexibilityTracker.adjustJobsRequiredConstraints(js,
+                                -js.getNumRequiredFlexibleConstraints());
+                    } else {
+                        long nextTimeElapsed =
+                                getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+                        if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)
+                                && nextTimeElapsed != NO_LIFECYCLE_END) {
+                            mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
+                        }
+                    }
+                    if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
+                        changedJobs.add(js);
                     }
                 }
+                mStateChangedListener.onControllerStateChanged(changedJobs);
             }
         }
     }
 
     @VisibleForTesting
-    class FcConstants {
+    class FcConfig {
         private boolean mShouldReevaluateConstraints = false;
 
-        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
-
-        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
-
         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
-        private static final String FC_CONSTANT_PREFIX = "fc_";
+        private static final String FC_CONFIG_PREFIX = "fc_";
 
-        static final String KEY_FLEXIBILITY_ENABLED = FC_CONSTANT_PREFIX + "enable_flexibility";
+        static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility";
+        static final String KEY_DEADLINE_PROXIMITY_LIMIT =
+                FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
+        static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
+                FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+        static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms";
+        static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+                FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
 
-        // TODO(b/239925946): properly handle DeviceConfig and changing variables
+        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+        @VisibleForTesting
+        static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
+        @VisibleForTesting
+        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+        private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
+        @VisibleForTesting
+        final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+
+        /**
+         * If false the controller will not track new jobs
+         * and the flexibility constraint will always be satisfied.
+         */
+        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+        /** How close to a jobs' deadline all flexible constraints will be dropped. */
+        public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
+        /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
+        public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+        public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+        /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
+        public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+                DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+
         @GuardedBy("mLock")
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
@@ -410,11 +656,79 @@
                     if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
                         mFlexibilityEnabled = FLEXIBILITY_ENABLED;
                         mShouldReevaluateConstraints = true;
+                        if (mFlexibilityEnabled) {
+                            mPrefetchController
+                                    .registerPrefetchChangedListener(mPrefetchChangedListener);
+                        } else {
+                            mPrefetchController
+                                    .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
+                        }
+                    }
+                    break;
+                case KEY_DEADLINE_PROXIMITY_LIMIT:
+                    DEADLINE_PROXIMITY_LIMIT_MS =
+                            properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS);
+                    if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) {
+                        mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_FALLBACK_FLEXIBILITY_DEADLINE:
+                    FALLBACK_FLEXIBILITY_DEADLINE_MS =
+                            properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
+                    if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
+                        mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS:
+                    MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                            properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS);
+                    if (mMinTimeBetweenFlexibilityAlarmsMs
+                            != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) {
+                        mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
+                    String dropPercentString = properties.getString(key, "");
+                    PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+                            parsePercentToDropString(dropPercentString);
+                    if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
+                            && !Arrays.equals(mPercentToDropConstraints,
+                            PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
+                        mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+                        mShouldReevaluateConstraints = true;
                     }
                     break;
             }
         }
 
+        private int[] parsePercentToDropString(String s) {
+            String[] dropPercentString = s.split(",");
+            int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS];
+            if (dropPercentInt.length != dropPercentString.length) {
+                return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+            }
+            int prevPercent = 0;
+            for (int i = 0; i < dropPercentString.length; i++) {
+                try {
+                    dropPercentInt[i] =
+                            Integer.parseInt(dropPercentString[i]);
+                } catch (NumberFormatException ex) {
+                    Slog.e(TAG, "Provided string was improperly formatted.", ex);
+                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                }
+                if (dropPercentInt[i] < prevPercent) {
+                    Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
+                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                }
+                prevPercent = dropPercentInt[i];
+            }
+
+            return dropPercentInt;
+        }
+
         private void dump(IndentingPrintWriter pw) {
             pw.println();
             pw.print(FlexibilityController.class.getSimpleName());
@@ -429,8 +743,8 @@
 
     @VisibleForTesting
     @NonNull
-    FcConstants getFcConstants() {
-        return mFcConstants;
+    FcConfig getFcConfig() {
+        return mFcConfig;
     }
 
     @Override
@@ -440,6 +754,6 @@
         pw.println();
 
         mFlexibilityTracker.dump(pw, predicate);
-        mFcConstants.dump(pw);
+        mFcConfig.dump(pw);
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 52882fe..57c7317 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -51,6 +51,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -574,7 +575,6 @@
 
         if (!isRequestedExpeditedJob()
                 && satisfiesMinWindowException
-                && !job.isPrefetch()
                 && lacksSomeFlexibleConstraints) {
             mNumRequiredFlexibleConstraints =
                     NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
@@ -1669,7 +1669,8 @@
         return readinessStatusWithConstraint(constraint, true);
     }
 
-    private boolean readinessStatusWithConstraint(int constraint, boolean value) {
+    @VisibleForTesting
+    boolean readinessStatusWithConstraint(int constraint, boolean value) {
         boolean oldValue = false;
         int satisfied = mSatisfiedConstraintsOfInterest;
         switch (constraint) {
@@ -1705,6 +1706,15 @@
                 break;
         }
 
+        // The flexibility constraint relies on other constraints to be satisfied.
+        // This function lacks the information to determine if flexibility will be satisfied.
+        // But for the purposes of this function it is still useful to know the jobs' readiness
+        // not including the flexibility constraint. If flexibility is the constraint in question
+        // we can proceed as normal.
+        if (constraint != CONSTRAINT_FLEXIBLE) {
+            satisfied |= CONSTRAINT_FLEXIBLE;
+        }
+
         boolean toReturn = isReady(satisfied);
 
         switch (constraint) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 0f385ef..0945b7e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -81,6 +81,8 @@
      */
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>();
     private final ThresholdAlarmListener mThresholdAlarmListener;
 
     /**
@@ -99,6 +101,13 @@
     @GuardedBy("mLock")
     private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS;
 
+    /** Called by Prefetch Controller after local cache has been updated */
+    public interface PrefetchChangedListener {
+        /** Callback to inform listeners when estimated launch times change. */
+        void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName,
+                long prevEstimatedLaunchTime, long newEstimatedLaunchTime);
+    }
+
     @SuppressWarnings("FieldCanBeLocal")
     private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
             new EstimatedLaunchTimeChangedListener() {
@@ -291,12 +300,17 @@
                 // Don't bother caching the value unless the app has scheduled prefetch jobs
                 // before. This is based on the assumption that if an app has scheduled a
                 // prefetch job before, then it will probably schedule another one again.
+                final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
                 mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);
 
                 if (!jobs.isEmpty()) {
                     final long now = sSystemClock.millis();
                     final long nowElapsed = sElapsedRealtimeClock.millis();
                     updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
+                    for (int i = 0; i < mPrefetchChangedListeners.size(); i++) {
+                        mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs,
+                                userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime);
+                    }
                     if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                         mStateChangedListener.onControllerStateChanged(jobs);
                     }
@@ -448,6 +462,18 @@
         }
     }
 
+    void registerPrefetchChangedListener(PrefetchChangedListener listener) {
+        synchronized (mLock) {
+            mPrefetchChangedListeners.add(listener);
+        }
+    }
+
+    void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) {
+        synchronized (mLock) {
+            mPrefetchChangedListeners.remove(listener);
+        }
+    }
+
     private class PcHandler extends Handler {
         PcHandler(Looper looper) {
             super(looper);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 8b8a57d..e23860c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -34,8 +34,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -46,6 +44,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -284,6 +283,7 @@
 
         for (int i = 0; i < pkgNames.size(); ++i) {
             final String pkgName = pkgNames.valueAt(i);
+            final boolean isVip = mIrs.isVip(userId, pkgName);
             SparseArrayMap<String, OngoingEvent> ongoingEvents =
                     mCurrentOngoingEvents.get(userId, pkgName);
             if (ongoingEvents != null) {
@@ -298,8 +298,8 @@
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
-                        final boolean isAffordable =
-                                isAffordableLocked(newBalance,
+                        final boolean isAffordable = isVip
+                                || isAffordableLocked(newBalance,
                                         note.getCachedModifiedPrice(), note.getCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
@@ -313,6 +313,51 @@
     }
 
     @GuardedBy("mLock")
+    void onVipStatusChangedLocked(final int userId, @NonNull String pkgName) {
+        final long now = getCurrentTimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
+
+        final boolean isVip = mIrs.isVip(userId, pkgName);
+        SparseArrayMap<String, OngoingEvent> ongoingEvents =
+                mCurrentOngoingEvents.get(userId, pkgName);
+        if (ongoingEvents != null) {
+            mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+            ongoingEvents.forEach(mOngoingEventUpdater);
+        }
+        final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
+                mActionAffordabilityNotes.get(userId, pkgName);
+        if (actionAffordabilityNotes != null) {
+            final int size = actionAffordabilityNotes.size();
+            final long newBalance =
+                    mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
+            for (int n = 0; n < size; ++n) {
+                final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
+                note.recalculateCosts(economicPolicy, userId, pkgName);
+                final boolean isAffordable = isVip
+                        || isAffordableLocked(newBalance,
+                        note.getCachedModifiedPrice(), note.getCtp());
+                if (note.isCurrentlyAffordable() != isAffordable) {
+                    note.setNewAffordability(isAffordable);
+                    mIrs.postAffordabilityChanged(userId, pkgName, note);
+                }
+            }
+        }
+        scheduleBalanceCheckLocked(userId, pkgName);
+    }
+
+    @GuardedBy("mLock")
+    void onVipStatusChangedLocked(@NonNull SparseSetArray<String> pkgs) {
+        for (int u = pkgs.size() - 1; u >= 0; --u) {
+            final int userId = pkgs.keyAt(u);
+
+            for (int p = pkgs.sizeAt(u) - 1; p >= 0; --p) {
+                onVipStatusChangedLocked(userId, pkgs.valueAt(u, p));
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
     private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
         final long now = getCurrentTimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
@@ -349,11 +394,12 @@
                 if (actionAffordabilityNotes != null) {
                     final int size = actionAffordabilityNotes.size();
                     final long newBalance = getBalanceLocked(userId, pkgName);
+                    final boolean isVip = mIrs.isVip(userId, pkgName);
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
-                        final boolean isAffordable =
-                                isAffordableLocked(newBalance,
+                        final boolean isAffordable = isVip
+                                || isAffordableLocked(newBalance,
                                         note.getCachedModifiedPrice(), note.getCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
@@ -454,6 +500,14 @@
                     "Tried to adjust system balance for " + appToString(userId, pkgName));
             return;
         }
+        if (mIrs.isVip(userId, pkgName)) {
+            // This could happen if the app was made a VIP after it started performing actions.
+            // Continue recording the transaction for debugging purposes, but don't let it change
+            // any numbers.
+            transaction = new Ledger.Transaction(
+                    transaction.startTimeMs, transaction.endTimeMs,
+                    transaction.eventId, transaction.tag, 0 /* delta */, transaction.ctp);
+        }
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
         final long originalBalance = ledger.getCurrentBalance();
         if (transaction.delta > 0
@@ -479,10 +533,11 @@
                     mActionAffordabilityNotes.get(userId, pkgName);
             if (actionAffordabilityNotes != null) {
                 final long newBalance = ledger.getCurrentBalance();
+                final boolean isVip = mIrs.isVip(userId, pkgName);
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
-                    final boolean isAffordable =
-                            isAffordableLocked(newBalance,
+                    final boolean isAffordable = isVip
+                            || isAffordableLocked(newBalance,
                                     note.getCachedModifiedPrice(), note.getCtp());
                     if (note.isCurrentlyAffordable() != isAffordable) {
                         note.setNewAffordability(isAffordable);
@@ -606,13 +661,12 @@
     }
 
     /** Returns true if an app should be given credits in the general distributions. */
-    private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) {
-        final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+    private boolean shouldGiveCredits(@NonNull InstalledPackageInfo packageInfo) {
         // Skip apps that wouldn't be doing any work. Giving them ARCs would be wasteful.
-        if (applicationInfo == null || !applicationInfo.hasCode()) {
+        if (!packageInfo.hasCode) {
             return false;
         }
-        final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+        final int userId = UserHandle.getUserId(packageInfo.uid);
         // No point allocating ARCs to the system. It can do whatever it wants.
         return !mIrs.isSystem(userId, packageInfo.packageName);
     }
@@ -623,15 +677,15 @@
 
     @GuardedBy("mLock")
     void distributeBasicIncomeLocked(int batteryLevel) {
-        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+        final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages();
 
         final long now = getCurrentTimeMillis();
         for (int i = 0; i < pkgs.size(); ++i) {
-            final PackageInfo pkgInfo = pkgs.get(i);
+            final InstalledPackageInfo pkgInfo = pkgs.get(i);
             if (!shouldGiveCredits(pkgInfo)) {
                 continue;
             }
-            final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
+            final int userId = UserHandle.getUserId(pkgInfo.uid);
             final String pkgName = pkgInfo.packageName;
             final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
             final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
@@ -659,11 +713,11 @@
 
     @GuardedBy("mLock")
     void grantBirthrightsLocked(final int userId) {
-        final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
+        final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages(userId);
         final long now = getCurrentTimeMillis();
 
         for (int i = 0; i < pkgs.size(); ++i) {
-            final PackageInfo packageInfo = pkgs.get(i);
+            final InstalledPackageInfo packageInfo = pkgs.get(i);
             if (!shouldGiveCredits(packageInfo)) {
                 continue;
             }
@@ -869,7 +923,7 @@
     private void scheduleBalanceCheckLocked(final int userId, @NonNull final String pkgName) {
         SparseArrayMap<String, OngoingEvent> ongoingEvents =
                 mCurrentOngoingEvents.get(userId, pkgName);
-        if (ongoingEvents == null) {
+        if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
             // No ongoing transactions. No reason to schedule
             mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
             return;
@@ -1062,9 +1116,10 @@
                 note.setNewAffordability(true);
                 return;
             }
+            final boolean isVip = mIrs.isVip(userId, pkgName);
             note.recalculateCosts(economicPolicy, userId, pkgName);
-            note.setNewAffordability(
-                    isAffordableLocked(getBalanceLocked(userId, pkgName),
+            note.setNewAffordability(isVip
+                    || isAffordableLocked(getBalanceLocked(userId, pkgName),
                             note.getCachedModifiedPrice(), note.getCtp()));
             mIrs.postAffordabilityChanged(userId, pkgName, note);
             // Update ongoing alarm
@@ -1203,11 +1258,12 @@
                         if (actionAffordabilityNotes != null
                                 && actionAffordabilityNotes.size() > 0) {
                             final long newBalance = getBalanceLocked(userId, pkgName);
+                            final boolean isVip = mIrs.isVip(userId, pkgName);
 
                             for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                                 final ActionAffordabilityNote note =
                                         actionAffordabilityNotes.valueAt(i);
-                                final boolean isAffordable = isAffordableLocked(
+                                final boolean isAffordable = isVip || isAffordableLocked(
                                         newBalance, note.getCachedModifiedPrice(), note.getCtp());
                                 if (note.isCurrentlyAffordable() != isAffordable) {
                                     note.setNewAffordability(isAffordable);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index a46430f..aa66e92 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -149,7 +149,6 @@
     private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
-    private final InternalResourceService mInternalResourceService;
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -157,7 +156,6 @@
 
     AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
         super(irs);
-        mInternalResourceService = irs;
         mInjector = injector;
         loadConstants("", null);
     }
@@ -165,14 +163,14 @@
     @Override
     void setup(@NonNull DeviceConfig.Properties properties) {
         super.setup(properties);
-        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        ContentResolver resolver = mIrs.getContext().getContentResolver();
         loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
                 properties);
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
-        if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+        if (mIrs.isPackageExempted(userId, pkgName)) {
             return mMinSatiatedBalanceExempted;
         }
         // TODO: take other exemptions into account
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 0937e7b..564ffb9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -169,9 +169,11 @@
         }
     }
 
+    protected final InternalResourceService mIrs;
     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
 
     EconomicPolicy(@NonNull InternalResourceService irs) {
+        mIrs = irs;
         for (int mId : getCostModifiers()) {
             initModifier(mId, irs);
         }
@@ -240,7 +242,7 @@
     @NonNull
     final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
         final Action action = getAction(actionId);
-        if (action == null) {
+        if (action == null || mIrs.isVip(userId, pkgName)) {
             return new Cost(0, 0);
         }
         long ctp = action.costToProduce;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
new file mode 100644
index 0000000..da544bb
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tare;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+
+/** POJO to cache only the information about installed packages that TARE cares about. */
+class InstalledPackageInfo {
+    static final int NO_UID = -1;
+
+    public final int uid;
+    public final String packageName;
+    public final boolean hasCode;
+
+    InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+        final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+        this.uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
+        this.packageName = packageInfo.packageName;
+        this.hasCode = applicationInfo != null && applicationInfo.hasCode();
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 6d5c160..7a7d669 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -48,6 +48,7 @@
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -131,7 +132,7 @@
 
     @NonNull
     @GuardedBy("mLock")
-    private final List<PackageInfo> mPkgCache = new ArrayList<>();
+    private final List<InstalledPackageInfo> mPkgCache = new ArrayList<>();
 
     /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
     @GuardedBy("mLock")
@@ -149,6 +150,9 @@
     @GuardedBy("mLock")
     private ArraySet<String> mExemptedApps = new ArraySet<>();
 
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+
     private volatile boolean mIsEnabled;
     private volatile int mBootPhase;
     private volatile boolean mExemptListLoaded;
@@ -303,7 +307,7 @@
     }
 
     @NonNull
-    List<PackageInfo> getInstalledPackages() {
+    List<InstalledPackageInfo> getInstalledPackages() {
         synchronized (mLock) {
             return mPkgCache;
         }
@@ -311,13 +315,12 @@
 
     /** Returns the installed packages for the specified user. */
     @NonNull
-    List<PackageInfo> getInstalledPackages(final int userId) {
-        final List<PackageInfo> userPkgs = new ArrayList<>();
+    List<InstalledPackageInfo> getInstalledPackages(final int userId) {
+        final List<InstalledPackageInfo> userPkgs = new ArrayList<>();
         synchronized (mLock) {
             for (int i = 0; i < mPkgCache.size(); ++i) {
-                final PackageInfo packageInfo = mPkgCache.get(i);
-                if (packageInfo.applicationInfo != null
-                        && UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) {
+                final InstalledPackageInfo packageInfo = mPkgCache.get(i);
+                if (UserHandle.getUserId(packageInfo.uid) == userId) {
                     userPkgs.add(packageInfo);
                 }
             }
@@ -369,6 +372,21 @@
         return UserHandle.isCore(getUid(userId, pkgName));
     }
 
+    boolean isVip(final int userId, @NonNull String pkgName) {
+        synchronized (mLock) {
+            final Boolean override = mVipOverrides.get(userId, pkgName);
+            if (override != null) {
+                return override;
+            }
+        }
+        if (isSystem(userId, pkgName)) {
+            // The government, I mean the system, can create ARCs as it needs to in order to
+            // operate.
+            return true;
+        }
+        return false;
+    }
+
     void onBatteryLevelChanged() {
         synchronized (mLock) {
             final int newBatteryLevel = getCurrentBatteryLevel();
@@ -451,7 +469,7 @@
             mPackageToUidCache.add(userId, pkgName, uid);
         }
         synchronized (mLock) {
-            mPkgCache.add(packageInfo);
+            mPkgCache.add(new InstalledPackageInfo(packageInfo));
             mUidToPackageCache.add(uid, pkgName);
             // TODO: only do this when the user first launches the app (app leaves stopped state)
             mAgent.grantBirthrightLocked(userId, pkgName);
@@ -471,9 +489,10 @@
         }
         synchronized (mLock) {
             mUidToPackageCache.remove(uid, pkgName);
+            mVipOverrides.delete(userId, pkgName);
             for (int i = 0; i < mPkgCache.size(); ++i) {
-                PackageInfo pkgInfo = mPkgCache.get(i);
-                if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId
+                final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+                if (UserHandle.getUserId(pkgInfo.uid) == userId
                         && pkgName.equals(pkgInfo.packageName)) {
                     mPkgCache.remove(i);
                     break;
@@ -496,20 +515,24 @@
 
     void onUserAdded(final int userId) {
         synchronized (mLock) {
-            mPkgCache.addAll(
-                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+            final List<PackageInfo> pkgs =
+                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+            for (int i = pkgs.size() - 1; i >= 0; --i) {
+                mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+            }
             mAgent.grantBirthrightsLocked(userId);
         }
     }
 
     void onUserRemoved(final int userId) {
         synchronized (mLock) {
+            mVipOverrides.delete(userId);
             ArrayList<String> removedPkgs = new ArrayList<>();
             for (int i = mPkgCache.size() - 1; i >= 0; --i) {
-                PackageInfo pkgInfo = mPkgCache.get(i);
-                if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId) {
+                final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+                if (UserHandle.getUserId(pkgInfo.uid) == userId) {
                     removedPkgs.add(pkgInfo.packageName);
-                    mUidToPackageCache.remove(pkgInfo.applicationInfo.uid);
+                    mUidToPackageCache.remove(pkgInfo.uid);
                     mPkgCache.remove(i);
                     break;
                 }
@@ -659,8 +682,11 @@
                 LocalServices.getService(UserManagerInternal.class);
         final int[] userIds = userManagerInternal.getUserIds();
         for (int userId : userIds) {
-            mPkgCache.addAll(
-                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+            final List<PackageInfo> pkgs =
+                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+            for (int i = pkgs.size() - 1; i >= 0; --i) {
+                mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+            }
         }
     }
 
@@ -881,6 +907,15 @@
                 Binder.restoreCallingIdentity(identityToken);
             }
         }
+
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return (new TareShellCommand(InternalResourceService.this)).exec(
+                    this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
+                    args);
+        }
     }
 
     private final class LocalService implements EconomyManagerInternal {
@@ -932,9 +967,9 @@
             if (!mIsEnabled) {
                 return true;
             }
-            if (isSystem(userId, pkgName)) {
+            if (isVip(userId, pkgName)) {
                 // The government, I mean the system, can create ARCs as it needs to in order to
-                // operate.
+                // allow VIPs to operate.
                 return true;
             }
             // TODO: take temp-allowlist into consideration
@@ -960,7 +995,7 @@
             if (!mIsEnabled) {
                 return FOREVER_MS;
             }
-            if (isSystem(userId, pkgName)) {
+            if (isVip(userId, pkgName)) {
                 return FOREVER_MS;
             }
             long totalCostPerSecond = 0;
@@ -1131,6 +1166,47 @@
         }
     }
 
+    // Shell command infrastructure
+    int executeClearVip(@NonNull PrintWriter pw) {
+        synchronized (mLock) {
+            final SparseSetArray<String> changedPkgs = new SparseSetArray<>();
+            for (int u = mVipOverrides.numMaps() - 1; u >= 0; --u) {
+                final int userId = mVipOverrides.keyAt(u);
+
+                for (int p = mVipOverrides.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+                    changedPkgs.add(userId, mVipOverrides.keyAt(u, p));
+                }
+            }
+            mVipOverrides.clear();
+            if (mIsEnabled) {
+                mAgent.onVipStatusChangedLocked(changedPkgs);
+            }
+        }
+        pw.println("Cleared all VIP statuses");
+        return TareShellCommand.COMMAND_SUCCESS;
+    }
+
+    int executeSetVip(@NonNull PrintWriter pw,
+            int userId, @NonNull String pkgName, @Nullable Boolean newVipState) {
+        final boolean changed;
+        synchronized (mLock) {
+            final boolean wasVip = isVip(userId, pkgName);
+            if (newVipState == null) {
+                mVipOverrides.delete(userId, pkgName);
+            } else {
+                mVipOverrides.add(userId, pkgName, newVipState);
+            }
+            changed = isVip(userId, pkgName) != wasVip;
+            if (mIsEnabled && changed) {
+                mAgent.onVipStatusChangedLocked(userId, pkgName);
+            }
+        }
+        pw.println(appToString(userId, pkgName) + " VIP status set to " + newVipState + "."
+                + " Final VIP state changed? " + changed);
+        return TareShellCommand.COMMAND_SUCCESS;
+    }
+
+    // Dump infrastructure
     private static void dumpHelp(PrintWriter pw) {
         pw.println("Resource Economy (economy) dump options:");
         pw.println("  [-h|--help] [package] ...");
@@ -1168,6 +1244,29 @@
             pw.print("Exempted apps", mExemptedApps);
             pw.println();
 
+            boolean printedVips = false;
+            pw.println();
+            pw.print("VIPs:");
+            for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
+                final int userId = mVipOverrides.keyAt(u);
+
+                for (int p = 0; p < mVipOverrides.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mVipOverrides.keyAt(u, p);
+
+                    printedVips = true;
+                    pw.println();
+                    pw.print(appToString(userId, pkgName));
+                    pw.print("=");
+                    pw.print(mVipOverrides.valueAt(u, p));
+                }
+            }
+            if (printedVips) {
+                pw.println();
+            } else {
+                pw.print(" None");
+            }
+            pw.println();
+
             pw.println();
             mCompleteEconomicPolicy.dump(pw);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index e7db1ad..03c5fdd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -151,7 +151,6 @@
     private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
-    private final InternalResourceService mInternalResourceService;
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -159,7 +158,6 @@
 
     JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
         super(irs);
-        mInternalResourceService = irs;
         mInjector = injector;
         loadConstants("", null);
     }
@@ -167,14 +165,14 @@
     @Override
     void setup(@NonNull DeviceConfig.Properties properties) {
         super.setup(properties);
-        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        final ContentResolver resolver = mIrs.getContext().getContentResolver();
         loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
                 properties);
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
-        if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+        if (mIrs.isPackageExempted(userId, pkgName)) {
             return mMinSatiatedBalanceExempted;
         }
         // TODO: take other exemptions into account
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 8f7657e..ed915cd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -22,7 +22,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -210,11 +209,11 @@
         mRemainingConsumableCakes = 0;
 
         final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
-        final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
+        final List<InstalledPackageInfo> installedPackages = mIrs.getInstalledPackages();
         for (int i = 0; i < installedPackages.size(); ++i) {
-            final PackageInfo packageInfo = installedPackages.get(i);
-            if (packageInfo.applicationInfo != null) {
-                final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+            final InstalledPackageInfo packageInfo = installedPackages.get(i);
+            if (packageInfo.uid != InstalledPackageInfo.NO_UID) {
+                final int userId = UserHandle.getUserId(packageInfo.uid);
                 ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
                 if (pkgsForUser == null) {
                     pkgsForUser = new ArraySet<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
new file mode 100644
index 0000000..5e380b40
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.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 com.android.server.tare;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for TARE.
+ */
+public class TareShellCommand extends BasicShellCommandHandler {
+    static final int COMMAND_ERROR = -1;
+    static final int COMMAND_SUCCESS = 0;
+
+    private final InternalResourceService mIrs;
+
+    public TareShellCommand(@NonNull InternalResourceService irs) {
+        mIrs = irs;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd != null ? cmd : "") {
+                case "clear-vip":
+                    return runClearVip(pw);
+                case "set-vip":
+                    return runSetVip(pw);
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            pw.println("Exception: " + e);
+        }
+        return COMMAND_ERROR;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+
+        pw.println("TARE commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  clear-vip");
+        pw.println("    Clears all VIP settings resulting from previous calls using `set-vip` and");
+        pw.println("    resets them all to default.");
+        pw.println("  set-vip <USER_ID> <PACKAGE> <true|false|default>");
+        pw.println("    Designate the app as a Very Important Package or not. A VIP is allowed to");
+        pw.println("    do as much work as it wants, regardless of TARE state.");
+        pw.println("    The user ID must be an explicit user ID. USER_ALL, CURRENT, etc. are not");
+        pw.println("    supported.");
+        pw.println();
+    }
+
+    private void checkPermission(@NonNull String operation) throws Exception {
+        final int perm = mIrs.getContext()
+                .checkCallingOrSelfPermission(Manifest.permission.CHANGE_APP_IDLE_STATE);
+        if (perm != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Uid " + Binder.getCallingUid()
+                    + " not permitted to " + operation);
+        }
+    }
+
+    private int runClearVip(@NonNull PrintWriter pw) throws Exception {
+        checkPermission("clear vip");
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mIrs.executeClearVip(pw);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private int runSetVip(@NonNull PrintWriter pw) throws Exception {
+        checkPermission("modify vip");
+
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String pkgName = getNextArgRequired();
+        final String vipState = getNextArgRequired();
+        final Boolean isVip = "default".equals(vipState) ? null : Boolean.valueOf(vipState);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mIrs.executeSetVip(pw, userId, pkgName, isVip);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 7ed1a43..1eb4c02 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -275,6 +275,7 @@
   public static final class R.attr {
     ctor public R.attr();
     field public static final int absListViewStyle = 16842858; // 0x101006a
+    field public static final int accessibilityDataPrivate;
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
     field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -14005,49 +14006,49 @@
 
   public final class Bitmap implements android.os.Parcelable {
     method @NonNull public android.graphics.Bitmap asShared();
-    method @WorkerThread public boolean compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream);
-    method public android.graphics.Bitmap copy(android.graphics.Bitmap.Config, boolean);
-    method public void copyPixelsFromBuffer(java.nio.Buffer);
-    method public void copyPixelsToBuffer(java.nio.Buffer);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
-    method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
+    method @WorkerThread public boolean compress(@NonNull android.graphics.Bitmap.CompressFormat, int, @NonNull java.io.OutputStream);
+    method public android.graphics.Bitmap copy(@NonNull android.graphics.Bitmap.Config, boolean);
+    method public void copyPixelsFromBuffer(@NonNull java.nio.Buffer);
+    method public void copyPixelsToBuffer(@NonNull java.nio.Buffer);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
     method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture);
     method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
+    method @NonNull public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
     method public int describeContents();
     method public void eraseColor(@ColorInt int);
     method public void eraseColor(@ColorLong long);
-    method @CheckResult public android.graphics.Bitmap extractAlpha();
-    method @CheckResult public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+    method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha();
+    method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha(@Nullable android.graphics.Paint, int[]);
     method public int getAllocationByteCount();
     method public int getByteCount();
     method @NonNull public android.graphics.Color getColor(int, int);
     method @Nullable public android.graphics.ColorSpace getColorSpace();
-    method public android.graphics.Bitmap.Config getConfig();
+    method @NonNull public android.graphics.Bitmap.Config getConfig();
     method public int getDensity();
     method public int getGenerationId();
     method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
     method public int getHeight();
-    method public byte[] getNinePatchChunk();
+    method @Nullable public byte[] getNinePatchChunk();
     method @ColorInt public int getPixel(int, int);
-    method public void getPixels(@ColorInt int[], int, int, int, int, int, int);
+    method public void getPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
     method public int getRowBytes();
-    method public int getScaledHeight(android.graphics.Canvas);
-    method public int getScaledHeight(android.util.DisplayMetrics);
+    method public int getScaledHeight(@NonNull android.graphics.Canvas);
+    method public int getScaledHeight(@NonNull android.util.DisplayMetrics);
     method public int getScaledHeight(int);
-    method public int getScaledWidth(android.graphics.Canvas);
-    method public int getScaledWidth(android.util.DisplayMetrics);
+    method public int getScaledWidth(@NonNull android.graphics.Canvas);
+    method public int getScaledWidth(@NonNull android.util.DisplayMetrics);
     method public int getScaledWidth(int);
     method public int getWidth();
     method public boolean hasAlpha();
@@ -14056,21 +14057,21 @@
     method public boolean isPremultiplied();
     method public boolean isRecycled();
     method public void prepareToDraw();
-    method public void reconfigure(int, int, android.graphics.Bitmap.Config);
+    method public void reconfigure(int, int, @NonNull android.graphics.Bitmap.Config);
     method public void recycle();
-    method public boolean sameAs(android.graphics.Bitmap);
+    method @WorkerThread public boolean sameAs(@Nullable android.graphics.Bitmap);
     method public void setColorSpace(@NonNull android.graphics.ColorSpace);
-    method public void setConfig(android.graphics.Bitmap.Config);
+    method public void setConfig(@NonNull android.graphics.Bitmap.Config);
     method public void setDensity(int);
     method public void setHasAlpha(boolean);
     method public void setHasMipMap(boolean);
     method public void setHeight(int);
     method public void setPixel(int, int, @ColorInt int);
-    method public void setPixels(@ColorInt int[], int, int, int, int, int, int);
+    method public void setPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
     method public void setPremultiplied(boolean);
     method public void setWidth(int);
     method @Nullable public static android.graphics.Bitmap wrapHardwareBuffer(@NonNull android.hardware.HardwareBuffer, @Nullable android.graphics.ColorSpace);
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Bitmap> CREATOR;
     field public static final int DENSITY_NONE = 0; // 0x0
   }
@@ -23188,9 +23189,10 @@
 
   public abstract static class MediaRouter2.RouteCallback {
     ctor public MediaRouter2.RouteCallback();
-    method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
-    method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
-    method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
   }
 
   public class MediaRouter2.RoutingController {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 572101c..be84032 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13398,6 +13398,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackage(String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackageAnyPhone(String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearRadioPowerOffForReason(int);
     method public void dial(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
@@ -13443,6 +13444,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.PhoneCapability getPhoneCapability();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Set<java.lang.Integer> getRadioPowerOffReasons();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
@@ -13497,6 +13499,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestRadioPowerOffForReason(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void resetIms(int);
@@ -13521,9 +13524,9 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setNrDualConnectivityState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
@@ -13605,6 +13608,10 @@
     field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
     field public static final int RADIO_POWER_OFF = 0; // 0x0
     field public static final int RADIO_POWER_ON = 1; // 0x1
+    field public static final int RADIO_POWER_REASON_CARRIER = 2; // 0x2
+    field public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3; // 0x3
+    field public static final int RADIO_POWER_REASON_THERMAL = 1; // 0x1
+    field public static final int RADIO_POWER_REASON_USER = 0; // 0x0
     field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
     field public static final int SET_CARRIER_RESTRICTION_ERROR = 2; // 0x2
     field public static final int SET_CARRIER_RESTRICTION_NOT_SUPPORTED = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ddbccfa..9a3c8a0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,6 +797,7 @@
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+    field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f2ea060..9210958 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -41,6 +41,8 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
+import com.android.internal.os.TimeoutRecord;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -440,10 +442,13 @@
 
     /** Input dispatch timeout to a window, start the ANR process. Return the timeout extension,
      * in milliseconds, or 0 to abort dispatch. */
-    public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
+    public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem,
+            TimeoutRecord timeoutRecord);
+
     public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
             ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
-            boolean aboveSystem, String reason);
+            boolean aboveSystem, TimeoutRecord timeoutRecord);
+
     /**
      * App started responding to input events. This signal can be used to abort the ANR process and
      * hide the ANR dialog.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 36e1eee..4da957c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -147,8 +147,6 @@
 import android.net.PacProxyManager;
 import android.net.TetheringManager;
 import android.net.VpnManager;
-import android.net.lowpan.ILowpanManager;
-import android.net.lowpan.LowpanManager;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.VcnManager;
 import android.net.wifi.WifiFrameworkInitializer;
@@ -779,15 +777,6 @@
                         ctx.mMainThread.getHandler());
             }});
 
-        registerService(Context.LOWPAN_SERVICE, LowpanManager.class,
-                new CachedServiceFetcher<LowpanManager>() {
-            @Override
-            public LowpanManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.LOWPAN_SERVICE);
-                ILowpanManager service = ILowpanManager.Stub.asInterface(b);
-                return new LowpanManager(ctx.getOuterContext(), service);
-            }});
-
         registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
                 new CachedServiceFetcher<WifiNl80211Manager>() {
                     @Override
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 47bec61..24fe0db 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -83,11 +83,11 @@
     }
 
     /** Internal interface for proximity callback. */
-    public abstract static class ProximityUpdateCallbackInternal {
+    public interface ProximityUpdateCallbackInternal {
         /**
          * @param distance the estimated distance of the user (in meter)
          * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
          */
-        public abstract void onProximityUpdate(double distance);
+        void onProximityUpdate(double distance);
     }
 }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a3d595ef..e92be7c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1109,6 +1109,17 @@
     public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f;
 
     /**
+     * Enables the use of split screen aspect ratio. This allows an app to use all the available
+     * space in split mode avoiding letterboxing.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    @TestApi
+    public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L;
+
+    /**
      * Compares activity window layout min width/height with require space for multi window to
      * determine if it can be put into multi window mode.
      */
@@ -1317,8 +1328,8 @@
      * Returns true if the activity has maximum or minimum aspect ratio.
      * @hide
      */
-    public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) {
-        return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0;
+    public boolean hasFixedAspectRatio() {
+        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
@@ -1460,30 +1471,10 @@
     }
 
     /**
-     * Returns the min aspect ratio of this activity.
-     *
-     * This takes into account the minimum aspect ratio as defined in the app's manifest and
-     * possible overrides as per OVERRIDE_MIN_ASPECT_RATIO.
-     *
-     * In the rare cases where the manifest minimum aspect ratio is required, use
-     * {@code getManifestMinAspectRatio}.
+     * Returns the min aspect ratio of this activity as defined in the manifest file.
      * @hide
      */
-    public float getMinAspectRatio(@ScreenOrientation int orientation) {
-        if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
-                isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
-                        && !isFixedOrientationPortrait(orientation))) {
-            return mMinAspectRatio;
-        }
-
-        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
-            return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
-        }
-
-        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
-            return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
-        }
-
+    public float getMinAspectRatio() {
         return mMinAspectRatio;
     }
 
@@ -1512,7 +1503,13 @@
         }
     }
 
-    private boolean isChangeEnabled(long changeId) {
+    /**
+     * Checks if a changeId is enabled for the current user
+     * @param changeId The changeId to verify
+     * @return True of the changeId is enabled
+     * @hide
+     */
+    public boolean isChangeEnabled(long changeId) {
         return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
                 UserHandle.getUserHandleForUid(applicationInfo.uid));
     }
@@ -1633,12 +1630,9 @@
         if (getMaxAspectRatio() != 0) {
             pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
         }
-        final float minAspectRatio = getMinAspectRatio(screenOrientation);
+        final float minAspectRatio = getMinAspectRatio();
         if (minAspectRatio != 0) {
             pw.println(prefix + "minAspectRatio=" + minAspectRatio);
-            if (getManifestMinAspectRatio() !=  minAspectRatio) {
-                pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
-            }
         }
         if (supportsSizeChanges) {
             pw.println(prefix + "supportsSizeChanges=true");
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7092e43..7247ef7 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
@@ -674,6 +675,45 @@
     }
 
     /**
+     * Forwards BiometricStateListener to FaceService.
+     *
+     * @param listener new BiometricStateListener being added
+     * @hide
+     */
+    public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
+        try {
+            mService.registerBiometricStateListener(listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds a callback that gets called when the service registers all of the face
+     * authenticators (HALs).
+     *
+     * If the face authenticators are already registered when the callback is added, the
+     * callback is invoked immediately.
+     *
+     * The callback is automatically removed after it's invoked.
+     *
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void addAuthenticatorsRegisteredCallback(
+            IFaceAuthenticatorsRegisteredCallback callback) {
+        if (mService != null) {
+            try {
+                mService.addAuthenticatorsRegisteredCallback(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!");
+        }
+    }
+
+    /**
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
new file mode 100644
index 0000000..78f978d2
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.face;
+
+import android.hardware.face.FaceSensorPropertiesInternal;
+import java.util.List;
+
+/**
+ * Callback to notify FaceManager that FaceService has registered all of the
+ * face authenticators (HALs).
+ * See {@link android.hardware.face.IFaceService#registerAuthenticators}.
+ *
+ * @hide
+ */
+oneway interface IFaceAuthenticatorsRegisteredCallback {
+    /**
+     * Notifies FaceManager that all of the face authenticators have been registered.
+     *
+     * @param sensors A consolidated list of sensor properties for all of the authenticators.
+     */
+    void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors);
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 369248e..9b56f43 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,9 +17,11 @@
 
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -163,4 +165,11 @@
     // hidlSensors must be non-null and empty. See AuthService.java
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+
+    // Adds a callback which gets called when the service registers all of the face
+    // authenticators. The callback is automatically removed after it's invoked.
+    void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+
+    // Registers BiometricStateListener.
+    void registerBiometricStateListener(IBiometricStateListener listener);
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index cc7ed18..1ba9a04 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -202,8 +202,10 @@
     void setSidefpsController(in ISidefpsController controller);
 
     // Registers BiometricStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
 
     // Sends a power button pressed event to all listeners.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     oneway void onPowerPressed();
 }
diff --git a/core/java/android/os/NewUserResponse.java b/core/java/android/os/NewUserResponse.java
index c2aef8e..f48d9e5 100644
--- a/core/java/android/os/NewUserResponse.java
+++ b/core/java/android/os/NewUserResponse.java
@@ -61,4 +61,13 @@
     public @UserManager.UserOperationResult int getOperationResult() {
         return mOperationResult;
     }
+
+    @Override
+    public String toString() {
+        return "NewUserResponse{"
+                + "mUser="
+                + mUser
+                + ", mOperationResult=" + mOperationResult
+                + '}';
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 58162f6..16352b8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4191,7 +4191,11 @@
     })
     public boolean canAddMoreUsers(@NonNull String userType) {
         try {
-            return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+            if (userType.equals(USER_TYPE_FULL_GUEST)) {
+                return mService.canAddMoreUsersOfType(userType);
+            } else {
+                return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+            }
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 2483f99..b7adcb8 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -755,6 +755,13 @@
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
 
     /**
+     * Namespace for Vendor System Native Boot related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+
+    /**
      * Namespace for memory safety related features (e.g. MTE)
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07e48ac..baf3eeca 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5486,6 +5486,15 @@
         public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
 
         /**
+         * Whether desktop mode is enabled or not.
+         * 0 = off
+         * 1 = on
+         * @hide
+         */
+        @Readable
+        public static final String DESKTOP_MODE = "desktop_mode";
+
+        /**
          * IMPORTANT: If you add a new public settings you also have to add it to
          * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
          * it to PRIVATE_SETTINGS below. Also add a validator that can validate
@@ -5614,6 +5623,7 @@
             PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE_VENDOR_HINT);
+            PRIVATE_SETTINGS.add(DESKTOP_MODE);
         }
 
         /**
@@ -7812,6 +7822,16 @@
                 "accessibility_display_magnification_auto_update";
 
         /**
+         * Accessibility Window Magnification Allow diagonal scrolling value. The value is boolean.
+         * 1 : on, 0 : off
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING =
+                "accessibility_allow_diagonal_scrolling";
+
+
+        /**
          * Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
          * modified from an AccessibilityService using the SoftKeyboardController.
          *
@@ -14264,7 +14284,7 @@
          *
          * @hide
          */
-        public static final int DEFAULT_ENABLE_TARE = 0;
+        public static final int DEFAULT_ENABLE_TARE = 1;
 
         /**
          * Whether to enable the TARE AlarmManager economic policy or not.
@@ -17688,20 +17708,13 @@
                     "wear_activity_auto_resume_timeout_set_by_user";
 
             /**
-             * The maximum times that we are allowed to reset the activity auto-resume timeout
-             * timer.
-             * @hide
-             */
-            public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT =
-                    "wear_activity_auto_resume_timeout_max_reset_count";
-
-            /**
              * If burn in protection is enabled.
              * @hide
              */
             public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection";
 
             /**
+
              * Whether the device has combined location setting enabled.
              * @hide
              */
@@ -17762,6 +17775,37 @@
              * @hide
              */
             public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled";
+
+            /** The status of the early updates process.
+             * @hide
+             */
+            public static final String EARLY_UPDATES_STATUS = "early_updates_status";
+
+            /**
+             * Early updates not started
+             * @hide
+             */
+            public static final int EARLY_UPDATES_STATUS_NOT_STARTED = 0;
+            /**
+             * Early updates started and in progress
+             * @hide
+             */
+            public static final int EARLY_UPDATES_STATUS_STARTED = 1;
+            /**
+             * Early updates completed and was successful
+             * @hide
+             */
+            public static final int EARLY_UPDATES_STATUS_SUCCESS = 2;
+            /**
+             * Early updates skipped
+             * @hide
+             */
+            public static final int EARLY_UPDATES_STATUS_SKIPPED = 3;
+            /**
+             * Early updates aborted due to timeout
+             * @hide
+             */
+            public static final int EARLY_UPDATES_STATUS_ABORTED = 4;
         }
     }
 
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 7234145..ab71459 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -95,6 +95,16 @@
     /** Limits the max value for the triggered audio channel. */
     private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
 
+    /**
+     * The bundle key for proximity value
+     *
+     * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PROXIMITY_METERS =
+            "android.service.voice.extra.PROXIMITY_METERS";
+
     /** Confidence level in the trigger outcome. */
     @HotwordConfidenceLevelValue
     private final int mConfidenceLevel;
@@ -197,6 +207,14 @@
      * <p>The use of this method is discouraged, and support for it will be removed in future
      * versions of Android.
      *
+     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+     * will be stored to enable proximity logic. The proximity meters is provided by the system,
+     * on devices that support detecting proximity of nearby users, to help disambiguate which
+     * nearby device should respond. When the proximity is unknown, the proximity value will not
+     * be stored. This mapping will be excluded from the max bundle size calculation because this
+     * mapping is included after the result is returned from the hotword detector service.
+     *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
      */
@@ -315,8 +333,23 @@
                     "audioChannel");
         }
         if (!mExtras.isEmpty()) {
-            Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, getMaxBundleSize(),
-                    "extras");
+            // Remove the proximity key from the bundle before checking the bundle size. The
+            // proximity value is added after the privileged module and can avoid the
+            // maxBundleSize limitation.
+            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
+                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
+                mExtras.remove(EXTRA_PROXIMITY_METERS);
+                // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
+                // has parcelable size of 4, but the default bundle has parcelable size of 0.
+                if (mExtras.size() > 0) {
+                    Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+                            getMaxBundleSize(), "extras");
+                }
+                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+            } else {
+                Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+                        getMaxBundleSize(), "extras");
+            }
         }
     }
 
@@ -513,6 +546,14 @@
      * <p>The use of this method is discouraged, and support for it will be removed in future
      * versions of Android.
      *
+     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+     * will be stored to enable proximity logic. The proximity meters is provided by the system,
+     * on devices that support detecting proximity of nearby users, to help disambiguate which
+     * nearby device should respond. When the proximity is unknown, the proximity value will not
+     * be stored. This mapping will be excluded from the max bundle size calculation because this
+     * mapping is included after the result is returned from the hotword detector service.
+     *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
      */
@@ -813,6 +854,14 @@
          * <p>The use of this method is discouraged, and support for it will be removed in future
          * versions of Android.
          *
+         * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+         * will be stored to enable proximity logic. The proximity meters is provided by the system,
+         * on devices that support detecting proximity of nearby users, to help disambiguate which
+         * nearby device should respond. When the proximity is unknown, the proximity value will not
+         * be stored. This mapping will be excluded from the max bundle size calculation because this
+         * mapping is included after the result is returned from the hotword detector service.
+         *
          * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
          * that can be used to communicate with other processes.
          */
@@ -882,10 +931,10 @@
     }
 
     @DataClass.Generated(
-            time = 1625541522353L,
+            time = 1658357814396L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 4fbf4b5..bc8822c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1153,8 +1153,8 @@
                         mLayout.surfaceInsets.set(0, 0, 0, 0);
                     }
                     final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
-                            View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState, mTempControls, mSyncSeqIdBundle);
+                            View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
+                            mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index eae951f..b73ff901 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -115,6 +115,11 @@
     public static final String SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR =
             "settings_accessibility_simple_cursor";
 
+    /**
+     * Enable new language and keyboard settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -148,6 +153,7 @@
         DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
         DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -161,6 +167,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
         PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
         PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
     }
 
     /**
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3016473..afcec66 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -75,41 +75,42 @@
      * @param requestedWidth The width the window wants to be.
      * @param requestedHeight The height the window wants to be.
      * @param viewVisibility Window root view's visibility.
-     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
-     * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
-     * @param outFrame Rect in which is placed the new position/size on
-     * screen.
-     * @param outContentInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the content of the window should be
-     * placed.  This can be used to modify the window layout to ensure its
-     * contents are visible to the user, taking into account system windows
-     * like the status bar or a soft keyboard.
-     * @param outVisibleInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the window is actually completely visible
-     * to the user.  This can be used to temporarily scroll the window's
-     * contents to make sure the user can see it.  This is different than
-     * <var>outContentInsets</var> in that these insets change transiently,
-     * so complex relayout of the window should not happen based on them.
-     * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
-     * treat as real display. Example of such area is a chin in some models of wearable devices.
-     * @param outBackdropFrame Rect which is used draw the resizing background during a resize
-     * operation.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     * @param outFrames The window frames used by the client side for layout.
      * @param outMergedConfiguration New config container that holds global, override and merged
-     * config for window, if it is now becoming visible and the merged configuration has changed
-     * since it was last displayed.
-     * @param outSurface Object in which is placed the new display surface.
+     *                               config for window, if it is now becoming visible and the merged
+     *                               config has changed since it was last displayed.
+     * @param outSurfaceControl Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
-     *
-     * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
-     * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
+     * @param activeControls Objects which allow controlling {@link InsetsSource}s.
+     * @param bundle A temporary object to obtain the latest SyncSeqId.
+     * @return int Result flags, defined in {@link WindowManagerGlobal}.
      */
     int relayout(IWindow window, in WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility,
-            int flags, out ClientWindowFrames outFrames,
+            int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls,
             out Bundle bundle);
 
+    /**
+     * Similar to {@link #relayout} but this is an oneway method which doesn't return anything.
+     *
+     * @param window The window being modified.
+     * @param attrs If non-null, new attributes to apply to the window.
+     * @param requestedWidth The width the window wants to be.
+     * @param requestedHeight The height the window wants to be.
+     * @param viewVisibility Window root view's visibility.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     */
+    oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId);
+
     /*
      * Notify the window manager that an application is relaunching and
      * windows should be prepared for replacement.
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index d63c25a..5236fe7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -176,7 +176,9 @@
 
                 // If we have a new leash, make sure visibility is up-to-date, even though we
                 // didn't want to run an animation above.
-                applyRequestedVisibilityToControl();
+                if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+                    applyRequestedVisibilityToControl();
+                }
 
                 // Remove the surface that owned by last control when it lost.
                 if (!requestedVisible && lastControl == null) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70a5eda..8f9c5fe 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -119,10 +119,6 @@
     final int[] mLocation = new int[2];
 
     @UnsupportedAppUsage
-    // Used to ensure the Surface remains valid between SurfaceHolder#lockCanvas and
-    // SurfaceHolder#unlockCanvasAndPost calls. This prevents SurfaceView from destroying or
-    // invalidating the Surface. This means this lock should be acquired when destroying the
-    // BlastBufferQueue.
     final ReentrantLock mSurfaceLock = new ReentrantLock();
     @UnsupportedAppUsage
     final Surface mSurface = new Surface();       // Current surface in use
@@ -727,18 +723,14 @@
 
     private void releaseSurfaces(boolean releaseSurfacePackage) {
         mSurfaceAlpha = 1f;
-        try {
-            mSurfaceLock.lock();
-            mSurface.destroy();
+        mSurface.destroy();
+
+        synchronized (mSurfaceControlLock) {
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
             }
-        } finally {
-            mSurfaceLock.unlock();
-        }
 
-        synchronized (mSurfaceControlLock) {
             final Transaction transaction = new Transaction();
             if (mSurfaceControl != null) {
                 transaction.remove(mSurfaceControl);
@@ -1240,21 +1232,16 @@
                     .build();
         }
 
+        // Always recreate the IGBP for compatibility. This can be optimized in the future but
+        // the behavior change will need to be gated by SDK version.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
         mTransformHint = viewRoot.getBufferTransformHint();
         mBlastSurfaceControl.setTransformHint(mTransformHint);
 
-        // Always recreate the IGBP for compatibility. This can be optimized in the future but
-        // the behavior change will need to be gated by SDK version.
-        try {
-            mSurfaceLock.lock();
-            if (mBlastBufferQueue != null) {
-                mBlastBufferQueue.destroy();
-            }
-            mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
-            mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
-        } finally {
-            mSurfaceLock.unlock();
-        }
+        mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
+        mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
         mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
     }
 
@@ -1827,14 +1814,7 @@
             // so the next connect will always work if we end up reusing
             // the surface.
             if (mSurface.isValid()) {
-                // We need to grab this lock since mSurface.forceScopedDisconnect
-                // will free buffers from the queue.
-                try {
-                    mSurfaceLock.lock();
-                    mSurface.forceScopedDisconnect();
-                } finally {
-                    mSurfaceLock.unlock();
-                }
+                mSurface.forceScopedDisconnect();
             }
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b2854a3..674f0a2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -707,6 +708,8 @@
     final Rect mPendingBackDropFrame = new Rect();
 
     boolean mPendingAlwaysConsumeSystemBars;
+    private int mRelayoutSeq;
+    private final Rect mWinFrameInScreen = new Rect();
     private final InsetsState mTempInsets = new InsetsState();
     private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
     private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
@@ -3363,20 +3366,6 @@
                 }
             }
         } else {
-            // If a relayout isn't going to happen, we still need to check if this window can draw
-            // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
-            // have not been told by WMS that the sync is complete and that we can continue to draw
-            if (mCheckIfCanDraw) {
-                try {
-                    cancelDraw = mWindowSession.cancelDraw(mWindow);
-                    cancelReason = "wm_sync";
-                    if (DEBUG_BLAST) {
-                        Log.d(mTag, "cancelDraw returned " + cancelDraw);
-                    }
-                } catch (RemoteException e) {
-                }
-            }
-
             // Not the first pass and no window/insets/visibility change but the window
             // may have moved and we need check that and if so to update the left and right
             // in the attach info. We translate only the window frame since on window move
@@ -3385,6 +3374,20 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (!mRelayoutRequested && mCheckIfCanDraw) {
+            // We had a sync previously, but we didn't call IWindowSession#relayout in this
+            // traversal. So we don't know if the sync is complete that we can continue to draw.
+            // Here invokes cancelDraw to obtain the information.
+            try {
+                cancelDraw = mWindowSession.cancelDraw(mWindow);
+                cancelReason = "wm_sync";
+                if (DEBUG_BLAST) {
+                    Log.d(mTag, "cancelDraw returned " + cancelDraw);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
         if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
             // If the surface has been replaced, there's a chance the bounds layer is not parented
             // to the new layer. When updating bounds layer, also reparent to the main VRI
@@ -6402,6 +6405,12 @@
             // Make sure the fallback event policy sees all keys that will be
             // delivered to the view hierarchy.
             mFallbackEventHandler.preDispatchKeyEvent(event);
+
+            // Reset last tracked MotionEvent click toolType.
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mLastClickToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+            }
+
             return FORWARD;
         }
 
@@ -8150,7 +8159,43 @@
 
     private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
             boolean insetsPending) throws RemoteException {
-        mRelayoutRequested = true;
+        final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
+        final WindowConfiguration winConfigFromWm =
+                mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+        final WindowConfiguration winConfig = getCompatWindowConfiguration();
+        final int measuredWidth = mView.getMeasuredWidth();
+        final int measuredHeight = mView.getMeasuredHeight();
+        final boolean relayoutAsync;
+        if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+                && mWindowAttributes.type != TYPE_APPLICATION_STARTING
+                && mSyncSeqId <= mLastSyncSeqId
+                && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
+            final InsetsState state = mInsetsController.getState();
+            final Rect displayCutoutSafe = mTempRect;
+            state.getDisplayCutoutSafe(displayCutoutSafe);
+            mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
+                    state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+                    measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+                    1f /* compatScale */, mTmpFrames);
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
+            }
+
+            // If the position and the size of the frame are both changed, it will trigger a BLAST
+            // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
+            // need to send attributes via relayoutAsync.
+            final Rect oldFrame = mWinFrame;
+            final Rect newFrame = mTmpFrames.frame;
+            final boolean positionChanged =
+                    newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
+            final boolean sizeChanged =
+                    newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
+            relayoutAsync = !positionChanged || !sizeChanged;
+        } else {
+            relayoutAsync = false;
+        }
+
         float appScale = mAttachInfo.mApplicationScale;
         boolean restore = false;
         if (params != null && mTranslator != null) {
@@ -8172,26 +8217,47 @@
             }
         }
 
-        final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
-        final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
+        final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
+        final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
+        int relayoutResult = 0;
+        mRelayoutSeq++;
+        if (relayoutAsync) {
+            mWindowSession.relayoutAsync(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId);
+        } else {
+            relayoutResult = mWindowSession.relayout(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
+                    mTempInsets, mTempControls, mRelayoutBundle);
+            mRelayoutRequested = true;
+            final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+            if (maybeSyncSeqId > 0) {
+                mSyncSeqId = maybeSyncSeqId;
+            }
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+                mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+                mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+            }
+            mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+            mInsetsController.onStateChanged(mTempInsets);
+            mInsetsController.onControlsChanged(mTempControls);
 
-        int relayoutResult = mWindowSession.relayout(mWindow, params,
-                requestedWidth, requestedHeight, viewVisibility,
-                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
-                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
-                mTempControls, mRelayoutBundle);
-        final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
-        if (maybeSyncSeqId > 0) {
-            mSyncSeqId = maybeSyncSeqId;
+            mPendingAlwaysConsumeSystemBars =
+                    (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
         }
-        mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
                 (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
 
-        final WindowConfiguration winConfig = getCompatWindowConfiguration();
         WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
-                requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
+                requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
 
         final boolean transformHintChanged = transformHint != mLastTransformHint;
         final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
@@ -8238,23 +8304,11 @@
             destroySurface();
         }
 
-        mPendingAlwaysConsumeSystemBars =
-                (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
-
         if (restore) {
             params.restore();
         }
 
-        if (mTranslator != null) {
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
-            mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
-            mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
-        }
         setFrame(mTmpFrames.frame);
-        mInsetsController.onStateChanged(mTempInsets);
-        mInsetsController.onControlsChanged(mTempControls);
         return relayoutResult;
     }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d55c838..1ec17d0 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -286,10 +286,11 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         final State state;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
@@ -309,15 +310,23 @@
 
         if (viewFlags == View.VISIBLE) {
             t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
-            outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            if (outSurfaceControl != null) {
+                outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            }
         } else {
             t.hide(sc).apply();
-            outSurfaceControl.release();
+            if (outSurfaceControl != null) {
+                outSurfaceControl.release();
+            }
         }
-        outFrames.frame.set(0, 0, attrs.width, attrs.height);
-        outFrames.displayFrame.set(outFrames.frame);
+        if (outFrames != null) {
+            outFrames.frame.set(0, 0, attrs.width, attrs.height);
+            outFrames.displayFrame.set(outFrames.frame);
+        }
 
-        mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        if (outMergedConfiguration != null) {
+            outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        }
 
         if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
                 && state.mInputChannelToken != null) {
@@ -335,7 +344,7 @@
             }
         }
 
-        if (mInsetsState != null) {
+        if (outInsetsState != null && mInsetsState != null) {
             outInsetsState.set(mInsetsState);
         }
 
@@ -343,6 +352,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams inAttrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* outMergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncSeqIdBundle */);
+    }
+
+    @Override
     public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c03d10..8d3cf6d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -2086,15 +2085,6 @@
                 Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
                 return;
             }
-            if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
-                // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
-                // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
-                //  instead of mDisplayId.
-                mServedInputConnection.requestCursorUpdatesFromImm(
-                        CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR,
-                                CURSOR_UPDATE_FILTER_EDITOR_BOUNDS,
-                        mDisplayId);
-            }
 
             mServiceInvoker.startStylusHandwriting(mClient);
             // TODO(b/210039666): do we need any extra work for supporting non-native
@@ -2341,9 +2331,6 @@
         editorInfo.packageName = view.getContext().getOpPackageName();
         editorInfo.autofillId = view.getAutofillId();
         editorInfo.fieldId = view.getId();
-        synchronized (mH) {
-            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
-        }
         InputConnection ic = view.onCreateInputConnection(editorInfo);
         if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
 
@@ -2383,6 +2370,8 @@
                 startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
             }
 
+            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
+
             // Hook 'em up and let 'er rip.
             mCurrentEditorInfo = editorInfo.createCopyInternal();
             // Store the previously served connection so that we can determine whether it is safe
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b233e54..b21c5b3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,6 +226,8 @@
     final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
     boolean mAllowUndo = true;
 
+    private int mLastToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     // Cursor Controllers.
@@ -1732,6 +1734,9 @@
     @VisibleForTesting
     public void onTouchEvent(MotionEvent event) {
         final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
+
+        mLastToolType = event.getToolType(event.getActionIndex());
+
         mLastButtonState = event.getButtonState();
         if (filterOutEvent) {
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1784,7 +1789,7 @@
     }
 
     private void showFloatingToolbar() {
-        if (mTextActionMode != null) {
+        if (mTextActionMode != null && showUIForFingerInput()) {
             // Delay "show" so it doesn't interfere with click confirmations
             // or double-clicks that could "dismiss" the floating toolbar.
             int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1864,7 +1869,8 @@
             final CursorController cursorController = mTextView.hasSelection()
                     ? getSelectionController() : getInsertionController();
             if (cursorController != null && !cursorController.isActive()
-                    && !cursorController.isCursorBeingModified()) {
+                    && !cursorController.isCursorBeingModified()
+                    && showUIForFingerInput()) {
                 cursorController.show();
             }
         }
@@ -2515,6 +2521,10 @@
             return false;
         }
 
+        if (!showUIForFingerInput()) {
+            return false;
+        }
+
         ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
         mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
         registerOnBackInvokedCallback();
@@ -2667,7 +2677,7 @@
                     mTextView.postDelayed(mShowSuggestionRunnable,
                             ViewConfiguration.getDoubleTapTimeout());
                 } else if (hasInsertionController()) {
-                    if (shouldInsertCursor) {
+                    if (shouldInsertCursor && showUIForFingerInput()) {
                         getInsertionController().show();
                     } else {
                         getInsertionController().hide();
@@ -5397,7 +5407,8 @@
             final PointF showPosInView = new PointF();
             final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
                     && !tooLargeTextForMagnifier()
-                    && obtainMagnifierShowCoordinates(event, showPosInView);
+                    && obtainMagnifierShowCoordinates(event, showPosInView)
+                    && showUIForFingerInput();
             if (shouldShow) {
                 // Make the cursor visible and stop blinking.
                 mRenderCursorRegardlessTiming = true;
@@ -6343,6 +6354,15 @@
         }
     }
 
+    /**
+     * 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 showUIForFingerInput() {
+        return mLastToolType != MotionEvent.TOOL_TYPE_MOUSE;
+    }
+
     /** Controller for the insertion cursor. */
     @VisibleForTesting
     public class InsertionPointCursorController implements CursorController {
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index bd0a516..56310ae 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -11,3 +11,5 @@
 per-file TextView*,EditText.java,Editor.java,EditorTouchState.java = file:../text/OWNERS
 
 per-file SpellChecker.java = file:../view/inputmethod/OWNERS
+
+per-file RemoteViews* = file:../appwidget/OWNERS
\ No newline at end of file
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a0ec48b..54a415c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,11 @@
             final SelectionModifierCursorController controller = mEditor.getSelectionController();
             if (controller != null
                     && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
-                controller.show();
+                if (mEditor.showUIForFingerInput()) {
+                    controller.show();
+                } else {
+                    controller.hide();
+                }
             }
             if (result != null) {
                 switch (actionMode) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe0cbcb..1c7c582 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4887,20 +4887,28 @@
     }
 
     /**
-     * Set the line break style for text wrapping.
+     * Sets the line-break style for text wrapping.
      *
-     * The line break style to indicates the line break strategies can be used when
-     * calculating the text wrapping. The line break style affects rule-based breaking. It
-     * specifies the strictness of line-breaking rules.
-     * There are several types for the line break style:
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
-     * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
-     * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">
-     *         the line-break property</a>
+     * <p>Line-break style specifies the line-break strategies that can be used
+     * for text wrapping. The line-break style affects rule-based line breaking
+     * by specifying the strictness of line-breaking rules.</p>
      *
-     * @param lineBreakStyle the line break style for the text.
+     * <p>The following are types of line-break styles:</p>
+     * <ul>
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}</li>
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}</li>
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}</li>
+     * </ul>
+     *
+     * <p>The default line-break style is
+     * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
+     * line-breaking rules are used.</p>
+     *
+     * <p>See the
+     * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+     * line-break property</a> for more information.</p>
+     *
+     * @param lineBreakStyle The line break style for the text.
      */
     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
         if (mLineBreakStyle != lineBreakStyle) {
@@ -4914,17 +4922,22 @@
     }
 
     /**
-     * Set the line break word style for text wrapping.
+     * Sets the line-break word style for text wrapping.
      *
-     * The line break word style affects dictionary-based breaking and provide phrase-based
-     * breaking opportunities. The type for the line break word style is
-     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
-     * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
-     * is specified.
-     * See <a href="https://www.w3.org/TR/css-text-3/#word-break-property">
-     *         the word-break property</a>
+     * <p>The line-break word style affects dictionary-based line breaking by
+     * providing phrase-based line-breaking opportunities. Use
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
+     * phrase-based line breaking.</p>
      *
-     * @param lineBreakWordStyle the line break word style for the tet
+     * <p>The default line-break word style is
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
+     * no line-breaking word style is used.</p>
+     *
+     * <p>See the
+     * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
+     * word-break property</a> for more information.</p>
+     *
+     * @param lineBreakWordStyle The line break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
         mUserSpeficiedLineBreakwordStyle = true;
@@ -4939,18 +4952,18 @@
     }
 
     /**
-     * Get the current line break style for text wrapping.
+     * Gets the current line-break style for text wrapping.
      *
-     * @return the current line break style to be used for text wrapping.
+     * @return The line-break style to be used for text wrapping.
      */
     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
         return mLineBreakStyle;
     }
 
     /**
-     * Get the current line word break style for text wrapping.
+     * Gets the current line-break word style for text wrapping.
      *
-     * @return the current line break word style to be used for text wrapping.
+     * @return The line-break word style to be used for text wrapping.
      */
     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
         return mLineBreakWordStyle;
@@ -12489,7 +12502,7 @@
             RectF[] boundingRects = new RectF[positionInfoLength];
             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
             populateCharacterBounds(builder, positionInfoStartIndex,
-                    positionInfoStartIndex + positionInfoLength,
+                    Math.min(positionInfoStartIndex + positionInfoLength, length()),
                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
             for (int i = 0; i < positionInfoLength; i++) {
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 2bef10f..b63ce1b 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -968,27 +968,6 @@
         });
     }
 
-    /**
-     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
-     *
-     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
-     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param cursorUpdateFilter the filter for
-     *      {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param imeDisplayId displayId on which IME is displayed.
-     */
-    @Dispatching(cancellable = true)
-    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
-            int imeDisplayId) {
-        final int currentSessionId = mCurrentSessionId.get();
-        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
-            if (currentSessionId != mCurrentSessionId.get()) {
-                return;  // cancelled
-            }
-            requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
-        });
-    }
-
     @Dispatching(cancellable = true)
     @Override
     public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
new file mode 100644
index 0000000..a8885f9
--- /dev/null
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.SystemClock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A timeout that has triggered on the system.
+ *
+ * @hide
+ */
+public class TimeoutRecord {
+    /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+    @IntDef(value = {
+            TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW,
+            TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE,
+            TimeoutKind.BROADCAST_RECEIVER,
+            TimeoutKind.SERVICE_START,
+            TimeoutKind.SERVICE_EXEC,
+            TimeoutKind.CONTENT_PROVIDER,
+            TimeoutKind.APP_REGISTERED})
+
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface TimeoutKind {
+        int INPUT_DISPATCH_NO_FOCUSED_WINDOW = 1;
+        int INPUT_DISPATCH_WINDOW_UNRESPONSIVE = 2;
+        int BROADCAST_RECEIVER = 3;
+        int SERVICE_START = 4;
+        int SERVICE_EXEC = 5;
+        int CONTENT_PROVIDER = 6;
+        int APP_REGISTERED = 7;
+    }
+
+    /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+    @TimeoutKind
+    public final int mKind;
+
+    /** Reason for the timeout. */
+    public final String mReason;
+
+    /** System uptime in millis when the timeout was triggered. */
+    public final long mEndUptimeMillis;
+
+    /**
+     * Was the end timestamp taken right after the timeout triggered, before any potentially
+     * expensive operations such as taking locks?
+     */
+    public final boolean mEndTakenBeforeLocks;
+
+    private TimeoutRecord(@TimeoutKind int kind, @NonNull String reason, long endUptimeMillis,
+            boolean endTakenBeforeLocks) {
+        this.mKind = kind;
+        this.mReason = reason;
+        this.mEndUptimeMillis = endUptimeMillis;
+        this.mEndTakenBeforeLocks = endTakenBeforeLocks;
+    }
+
+    private static TimeoutRecord endingNow(@TimeoutKind int kind, String reason) {
+        long endUptimeMillis = SystemClock.uptimeMillis();
+        return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ true);
+    }
+
+    private static TimeoutRecord endingApproximatelyNow(@TimeoutKind int kind, String reason) {
+        long endUptimeMillis = SystemClock.uptimeMillis();
+        return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ false);
+    }
+
+    /** Record for a broadcast receiver timeout. */
+    @NonNull
+    public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+    }
+
+    /** Record for an input dispatch no focused window timeout */
+    @NonNull
+    public static TimeoutRecord forInputDispatchNoFocusedWindow(@NonNull String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, reason);
+    }
+
+    /** Record for an input dispatch window unresponsive timeout. */
+    @NonNull
+    public static TimeoutRecord forInputDispatchWindowUnresponsive(@NonNull String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, reason);
+    }
+
+    /** Record for a service exec timeout. */
+    @NonNull
+    public static TimeoutRecord forServiceExec(@NonNull String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason);
+    }
+
+    /** Record for a service start timeout. */
+    @NonNull
+    public static TimeoutRecord forServiceStartWithEndTime(@NonNull String reason,
+            long endUptimeMillis) {
+        return new TimeoutRecord(TimeoutKind.SERVICE_START, reason,
+                endUptimeMillis, /* endTakenBeforeLocks */ true);
+    }
+
+    /** Record for a content provider timeout. */
+    @NonNull
+    public static TimeoutRecord forContentProvider(@NonNull String reason) {
+        return TimeoutRecord.endingApproximatelyNow(TimeoutKind.CONTENT_PROVIDER, reason);
+    }
+
+    /** Record for an app registered timeout. */
+    @NonNull
+    public static TimeoutRecord forApp(@NonNull String reason) {
+        return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
+    }
+}
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index 5e34c15..134a917 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -137,4 +137,13 @@
         }
         return false;
     }
+
+    @Override
+    public boolean isConfigurationContext() {
+        Context context = mContext.get();
+        if (context != null) {
+            return context.isConfigurationContext();
+        }
+        return false;
+    }
 }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index b9243ec..9474f6f 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,6 +1,7 @@
 package com.android.internal.util;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,7 +28,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.WindowManager;
 import android.view.WindowManager.ScreenshotSource;
 import android.view.WindowManager.ScreenshotType;
 
@@ -42,10 +42,15 @@
     public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
 
     /**
-     * Describes a screenshot request (to make it easier to pass data through to the handler).
+     * Describes a screenshot request.
      */
     public static class ScreenshotRequest implements Parcelable {
+        @ScreenshotType
+        private final int mType;
+
+        @ScreenshotSource
         private final int mSource;
+
         private final Bundle mBitmapBundle;
         private final Rect mBoundsInScreen;
         private final Insets mInsets;
@@ -53,20 +58,27 @@
         private final int mUserId;
         private final ComponentName mTopComponent;
 
-        @VisibleForTesting
-        public ScreenshotRequest(int source) {
-            mSource = source;
-            mBitmapBundle = null;
-            mBoundsInScreen = null;
-            mInsets = null;
-            mTaskId = -1;
-            mUserId = -1;
-            mTopComponent = null;
+
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
+            this(type, source, /* topComponent */ null);
         }
 
-        @VisibleForTesting
-        public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
-                Insets insets, int taskId, int userId, ComponentName topComponent) {
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+                ComponentName topComponent) {
+            this(type,
+                source,
+                /* bitmapBundle*/ null,
+                /* boundsInScreen */ null,
+                /* insets */ null,
+                /* taskId */ -1,
+                /* userId */ -1,
+                topComponent);
+        }
+
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+                Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
+                ComponentName topComponent) {
+            mType = type;
             mSource = source;
             mBitmapBundle = bitmapBundle;
             mBoundsInScreen = boundsInScreen;
@@ -77,6 +89,7 @@
         }
 
         ScreenshotRequest(Parcel in) {
+            mType = in.readInt();
             mSource = in.readInt();
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
@@ -96,6 +109,12 @@
             }
         }
 
+        @ScreenshotType
+        public int getType() {
+            return mType;
+        }
+
+        @ScreenshotSource
         public int getSource() {
             return mSource;
         }
@@ -131,6 +150,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mType);
             dest.writeInt(mSource);
             if (mBitmapBundle == null) {
                 dest.writeInt(0);
@@ -208,8 +228,7 @@
          * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
          *
          * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
-         * this
-         * Bitmap on to any other source.
+         * this Bitmap on to any other source.
          *
          * @param bundle containing the bitmap
          * @return a hardware Bitmap
@@ -261,16 +280,16 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param type The type of screenshot, defined by {@link ScreenshotType}
      * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
      * @param handler used to process messages received from the screenshot service
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
      *         null if no screenshot was saved
      */
-    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
             @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
-        takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
                 completionConsumer);
     }
 
@@ -280,7 +299,7 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param type The type of screenshot, defined by {@link ScreenshotType}
      * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
      * @param handler used to process messages received from the screenshot service
      * @param timeoutMs time limit for processing, intended only for testing
@@ -288,10 +307,10 @@
      *         null if no screenshot was saved
      */
     @VisibleForTesting
-    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
             @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
-        takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+        takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
     }
 
     /**
@@ -312,14 +331,12 @@
             @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
             @ScreenshotSource int source, @NonNull Handler handler,
             @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
-                boundsInScreen, insets, taskId, userId, topComponent);
-        takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
-                SCREENSHOT_TIMEOUT_MS,
-                completionConsumer);
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
+                source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
+        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
     }
 
-    private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+    private void takeScreenshot(@NonNull Handler handler,
             ScreenshotRequest screenshotRequest, long timeoutMs,
             @Nullable Consumer<Uri> completionConsumer) {
         synchronized (mScreenshotLock) {
@@ -337,7 +354,7 @@
                 }
             };
 
-            Message msg = Message.obtain(null, screenshotType, screenshotRequest);
+            Message msg = Message.obtain(null, 0, screenshotRequest);
 
             Handler h = new Handler(handler.getLooper()) {
                 @Override
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723..b11ea29 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Size;
 
@@ -108,6 +109,12 @@
                 }
                 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.
+                    return icon.loadDrawable(context);
+                }
+
                 Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
                 if (result != null) {
                     return tintDrawable(icon, result);
diff --git a/core/proto/android/os/processstarttime.proto b/core/proto/android/os/processstarttime.proto
deleted file mode 100644
index d0f8bae..0000000
--- a/core/proto/android/os/processstarttime.proto
+++ /dev/null
@@ -1,92 +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.
- */
-
-syntax = "proto2";
-package android.os;
-
-option java_multiple_files = true;
-
-// This message is used for statsd logging and should be kept in sync with
-// frameworks/proto_logging/stats/atoms.proto
-/**
- * Logs information about process start time.
- *
- * Logged from:
- *      frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message ProcessStartTime {
-  // The uid of the ProcessRecord.
-  optional int32 uid = 1;
-
-  // The process pid.
-  optional int32 pid = 2;
-
-  // The process name.
-  // Usually package name, "system" for system server.
-  // Provided by ActivityManagerService.
-  optional string process_name = 3;
-
-  enum StartType {
-    UNKNOWN = 0;
-    WARM = 1;
-    HOT = 2;
-    COLD = 3;
-  }
-
-  // The start type.
-  optional StartType type = 4;
-
-  // The elapsed realtime at the start of the process.
-  optional int64 process_start_time_millis = 5;
-
-  // Number of milliseconds it takes to reach bind application.
-  optional int32 bind_application_delay_millis = 6;
-
-  // Number of milliseconds it takes to finish start of the process.
-  optional int32 process_start_delay_millis = 7;
-
-  // hostingType field in ProcessRecord, the component type such as "activity",
-  // "service", "content provider", "broadcast" or other strings.
-  optional string hosting_type = 8;
-
-  // hostingNameStr field in ProcessRecord. The component class name that runs
-  // in this process.
-  optional string hosting_name = 9;
-
-  // Broadcast action name.
-  optional string broadcast_action_name = 10;
-
-  enum HostingTypeId {
-    HOSTING_TYPE_UNKNOWN = 0;
-    HOSTING_TYPE_ACTIVITY = 1;
-    HOSTING_TYPE_ADDED_APPLICATION = 2;
-    HOSTING_TYPE_BACKUP = 3;
-    HOSTING_TYPE_BROADCAST = 4;
-    HOSTING_TYPE_CONTENT_PROVIDER = 5;
-    HOSTING_TYPE_LINK_FAIL = 6;
-    HOSTING_TYPE_ON_HOLD = 7;
-    HOSTING_TYPE_NEXT_ACTIVITY = 8;
-    HOSTING_TYPE_NEXT_TOP_ACTIVITY = 9;
-    HOSTING_TYPE_RESTART = 10;
-    HOSTING_TYPE_SERVICE = 11;
-    HOSTING_TYPE_SYSTEM = 12;
-    HOSTING_TYPE_TOP_ACTIVITY = 13;
-    HOSTING_TYPE_EMPTY = 14;
-  }
-
-  optional HostingTypeId hosting_type_id = 11;
-}
-
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
new file mode 100644
index 0000000..96860b0
--- /dev/null
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:minWidth="350dp"
+              android:layout_gravity="center"
+              android:background="@color/side_fps_toast_background">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/fp_power_button_enrollment_title"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textColor="@color/side_fps_text_color"
+        android:paddingLeft="20dp"/>
+    <Space
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"/>
+    <Button
+        android:id="@+id/turn_off_screen"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/fp_power_button_enrollment_button_text"
+        android:paddingRight="20dp"
+        style="?android:attr/buttonBarNegativeButtonStyle"
+        android:textColor="@color/side_fps_button_color"
+        android:maxLines="1"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index e2db49c..88171ce 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -41,4 +41,9 @@
     <!-- Lily Language Picker language item view colors -->
     <color name="language_picker_item_text_color">#F1F3F4</color>
     <color name="language_picker_item_text_color_secondary">#BDC1C6</color>
+
+    <!-- Color for side fps toast dark theme-->
+    <color name="side_fps_toast_background">#2E3132</color>
+    <color name="side_fps_text_color">#EFF1F2</color>
+    <color name="side_fps_button_color">#33B9DB</color>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 08471c3..c7153fc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3075,6 +3075,20 @@
             <enum name="noHideDescendants" value="4" />
         </attr>
 
+        <!-- Describes whether this view should allow interactions from AccessibilityServices only
+             if the service sets the isAccessibilityTool property. -->
+        <attr name="accessibilityDataPrivate" format="integer">
+            <!-- The system determines whether the view's accessibility data is private
+                 - default (recommended). -->
+            <enum name="auto" value="0" />
+            <!-- Allow interactions from AccessibilityServices only if the service sets the
+                 isAccessibilityTool property. -->
+            <enum name="yes" value="1" />
+            <!-- Allow interactions from all AccessibilityServices, regardless of their
+                 isAccessibilityTool property. -->
+            <enum name="no" value="2" />
+        </attr>
+
         <!-- Indicates to accessibility services whether the user should be notified when
              this view changes. -->
         <attr name="accessibilityLiveRegion" format="integer">
@@ -5528,22 +5542,22 @@
              ignores some hyphen character related typographic features, e.g. kerning. -->
             <enum name="fullFast" value="4" />
         </attr>
-        <!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
+        <!-- Specifies the line-break strategies for text wrapping. -->
         <attr name="lineBreakStyle">
-            <!-- No line break style specific. -->
+            <!-- No line-break rules are used for line breaking. -->
             <enum name="none" value="0" />
-            <!-- Use the least restrictive rule for line-breaking. -->
+            <!-- The least restrictive line-break rules are used for line breaking. -->
             <enum name="loose" value="1" />
-            <!-- Indicates breaking text with the most comment set of line-breaking rules. -->
+            <!-- The most common line-break rules are used for line breaking. -->
             <enum name="normal" value="2" />
-            <!-- Indicates breaking text with the most strictest line-breaking rules. -->
+            <!-- The most strict line-break rules are used for line breaking. -->
             <enum name="strict" value="3" />
         </attr>
-        <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+        <!-- Specifies the line-break word strategies for text wrapping.-->
         <attr name="lineBreakWordStyle">
-            <!-- No line break word style specific. -->
+            <!-- No line-break word style is used for line breaking. -->
             <enum name="none" value="0" />
-            <!-- Specify the phrase based breaking. -->
+            <!-- Line breaking is based on phrases, which results in text wrapping only on meaningful words. -->
             <enum name="phrase" value="1" />
         </attr>
         <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 062523e..b5c7ea6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2175,6 +2175,11 @@
         tag; often this is one of the {@link android.Manifest.permission standard
         system permissions}. -->
         <attr name="name" />
+        <!-- Optional: specify the minimum version of the Android OS for which the
+             application wishes to request the permission.  When running on a version
+             of Android lower than the number given here, the permission will not
+             be requested. -->
+        <attr name="minSdkVersion" format="integer|string" />
         <!-- Optional: specify the maximum version of the Android OS for which the
              application wishes to request the permission.  When running on a version
              of Android higher than the number given here, the permission will not
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index faf48f3..ac08327 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -454,4 +454,9 @@
     <!-- Lily Language Picker language item view colors -->
     <color name="language_picker_item_text_color">#202124</color>
     <color name="language_picker_item_text_color_secondary">#5F6368</color>
+
+    <!-- Color for side fps toast light theme -->
+    <color name="side_fps_toast_background">#F7F9FA</color>
+    <color name="side_fps_text_color">#191C1D</color>
+    <color name="side_fps_button_color">#00677E</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d176a1d..9faf5e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2004,6 +2004,9 @@
          on grouped devices. -->
     <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
 
+    <!-- Flag indicating current media Output Switcher version. -->
+    <integer name="config_mediaOutputSwitchDialogVersion">1</integer>
+
     <!-- Flag indicating that an outbound call must have a call capable phone account
          that has declared it can process the call's handle. -->
     <bool name="config_requireCallCapableAccountForHandle">false</bool>
@@ -3492,9 +3495,9 @@
          (for side fingerprint) -->
     <integer name="config_sidefpsPostAuthDowntime">400</integer>
 
-    <!-- The time (in millis) that a finger tap will wait for a power button
-         before dismissing the power dialog during enrollment(for side fingerprint) -->
-    <integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
+    <!-- The time (in millis) the clickable toast dialog will last until
+         automatically dismissing. This is currently used in SideFpsEventHandler -->
+    <integer name="config_sideFpsToastTimeout">3000</integer>
 
     <!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
          It declares the package name of VoiceInteractionService that should be started. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 11c245b..ad7d03e 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -114,6 +114,7 @@
     <public name="handwritingBoundsOffsetTop" />
     <public name="handwritingBoundsOffsetRight" />
     <public name="handwritingBoundsOffsetBottom" />
+    <public name="accessibilityDataPrivate" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3f2607..d3d0493 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3561,21 +3561,17 @@
     <!-- [CHAR LIMIT=NONE] Message to show in upgrading dialog when the bulk of the upgrade work is done. -->
     <string name="android_upgrading_complete">Finishing boot.</string>
 
-    <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
-    is pressed during fingerprint enrollment. -->
-    <string name="fp_power_button_enrollment_title">Continue setup?</string>
-
     <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
     button is pressed during fingerprint enrollment. -->
     <string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string>
 
+    <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+    is pressed during fingerprint enrollment. -->
+    <string name="fp_power_button_enrollment_title">Tap to turn off screen</string>
+
     <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
     power button is pressed during fingerprint enrollment. -->
-    <string name="fp_power_button_enrollment_positive_button">Turn off screen</string>
-
-    <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
-    power button is pressed during fingerprint enrollment. -->
-    <string name="fp_power_button_enrollment_negative_button">Continue setup</string>
+    <string name="fp_power_button_enrollment_button_text">Turn off screen</string>
 
     <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
     is pressed during biometric prompt when a side fingerprint sensor is present. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e6ae7b1..51712ff 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1849,8 +1849,7 @@
   <java-symbol type="string" name="fp_power_button_bp_negative_button" />
   <java-symbol type="string" name="fp_power_button_enrollment_title" />
   <java-symbol type="string" name="fp_power_button_enrollment_message" />
-  <java-symbol type="string" name="fp_power_button_enrollment_positive_button" />
-  <java-symbol type="string" name="fp_power_button_enrollment_negative_button" />
+  <java-symbol type="string" name="fp_power_button_enrollment_button_text" />
   <java-symbol type="string" name="global_actions" />
   <java-symbol type="string" name="global_action_power_off" />
   <java-symbol type="string" name="global_action_power_options" />
@@ -2627,7 +2626,11 @@
   <java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
   <java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
   <java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
-  <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
+  <java-symbol type="integer" name="config_sideFpsToastTimeout"/>
+
+  <!-- Clickable toast used during sidefps enrollment -->
+  <java-symbol type="layout" name="side_fps_toast" />
+  <java-symbol type="id" name="turn_off_screen" />
 
   <!-- Face authentication messages -->
   <java-symbol type="string" name="face_recalibrate_notification_name" />
@@ -4706,6 +4709,8 @@
 
   <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
 
+  <java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
+
   <!-- List of shared library packages that should be loaded by the classloader after the
        code and resources provided by applications. -->
   <java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 2054b4f..8cf118c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,8 +18,10 @@
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 
 import static junit.framework.Assert.assertEquals;
@@ -28,6 +30,7 @@
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -75,6 +78,7 @@
     private boolean mRemoveSurfaceCalled = false;
     private InsetsController mController;
     private InsetsState mState;
+    private ViewRootImpl mViewRoot;
 
     @Before
     public void setup() {
@@ -86,10 +90,9 @@
         instrumentation.runOnMainSync(() -> {
             final Context context = instrumentation.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            final ViewRootImpl viewRootImpl = new ViewRootImpl(context,
-                    context.getDisplayNoVerify());
+            mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, lets ignore BadTokenException.
             }
@@ -97,7 +100,7 @@
             mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
             mState.addSource(mSpyInsetsSource);
 
-            mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
+            mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
             mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
                     () -> mMockTransaction, mController) {
                 @Override
@@ -207,4 +210,40 @@
         });
 
     }
+
+    @Test
+    public void testWontUpdateImeLeashVisibility_whenAnimation() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsState state = new InsetsState();
+            ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
+            InsetsController insetsController = new InsetsController(host, (controller, type) -> {
+                if (type == ITYPE_IME) {
+                    return new InsetsSourceConsumer(ITYPE_IME, state,
+                            () -> mMockTransaction, controller) {
+                        @Override
+                        public int requestShow(boolean fromController) {
+                            return SHOW_IMMEDIATELY;
+                        }
+                    };
+                }
+                return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
+                        controller);
+            }, host.getHandler());
+            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
+
+            // Initial IME insets source control with its leash.
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+                    false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+            reset(mMockTransaction);
+
+            // Verify when the app requests controlling show IME animation, the IME leash
+            // visibility won't be updated when the consumer received the same leash in setControl.
+            insetsController.controlWindowInsetsAnimation(ime(), 0L,
+                    null /* interpolator */, null /* cancellationSignal */, null /* listener */);
+            assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+                    true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+            verify(mMockTransaction, never()).show(mLeash);
+        });
+    }
 }
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 c63d18b..0cee526 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@
         assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
 
     }
+
+    @Test
+    public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+            throws IOException {
+        Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        // This drawable must not be loaded - if it was, the code ignored the package specification.
+        assertThat(d).isNull();
+    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e0ec33e..03d22ac 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1123,6 +1123,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-1018968224": {
+      "message": "Recorded task is removed, so stop recording on display %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1016578046": {
       "message": "Moving to %s Relaunching %s callers=%s",
       "level": "INFO",
@@ -2275,6 +2281,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "96494268": {
+      "message": "Stop MediaProjection on virtual display %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "100936473": {
       "message": "Wallpaper animation!",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 857af11..318cd32 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -277,7 +277,7 @@
      * @see #setHeight(int)
      * @see #setConfig(Config)
      */
-    public void reconfigure(int width, int height, Config config) {
+    public void reconfigure(int width, int height, @NonNull Config config) {
         checkRecycled("Can't call reconfigure() on a recycled bitmap");
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("width and height must be > 0");
@@ -336,7 +336,7 @@
      * @see #setWidth(int)
      * @see #setHeight(int)
      */
-    public void setConfig(Config config) {
+    public void setConfig(@NonNull Config config) {
         reconfigure(getWidth(), getHeight(), config);
     }
 
@@ -590,7 +590,7 @@
      * in the buffer.</p>
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void copyPixelsToBuffer(Buffer dst) {
+    public void copyPixelsToBuffer(@NonNull Buffer dst) {
         checkHardware("unable to copyPixelsToBuffer, "
                 + "pixel access is not supported on Config#HARDWARE bitmaps");
         int elements = dst.remaining();
@@ -632,7 +632,7 @@
      * first rewind the buffer.</p>
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void copyPixelsFromBuffer(Buffer src) {
+    public void copyPixelsFromBuffer(@NonNull Buffer src) {
         checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
         checkHardware("unable to copyPixelsFromBuffer, Config#HARDWARE bitmaps are immutable");
 
@@ -686,7 +686,7 @@
      * @return the new bitmap, or null if the copy could not be made.
      * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
      */
-    public Bitmap copy(Config config, boolean isMutable) {
+    public Bitmap copy(@NonNull Config config, boolean isMutable) {
         checkRecycled("Can't copy a recycled bitmap");
         if (config == Config.HARDWARE && isMutable) {
             throw new IllegalArgumentException("Hardware bitmaps are always immutable");
@@ -791,6 +791,7 @@
      * @return The new scaled bitmap or the source bitmap if no scaling is required.
      * @throws IllegalArgumentException if width is <= 0, or height is <= 0
      */
+    @NonNull
     public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
             boolean filter) {
         Matrix m = new Matrix();
@@ -810,6 +811,7 @@
      * be the same object as source, or a copy may have been made.  It is
      * initialized with the same density and color space as the original bitmap.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap src) {
         return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
     }
@@ -830,6 +832,7 @@
      *         outside of the dimensions of the source bitmap, or width is <= 0,
      *         or height is <= 0
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
         return createBitmap(source, x, y, width, height, null, false);
     }
@@ -865,6 +868,7 @@
      *         outside of the dimensions of the source bitmap, or width is <= 0,
      *         or height is <= 0, or if the source bitmap has already been recycled
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
             @Nullable Matrix m, boolean filter) {
 
@@ -985,6 +989,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
         return createBitmap(width, height, config, true);
     }
@@ -1003,6 +1008,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width,
             int height, @NonNull Config config) {
         return createBitmap(display, width, height, config, true);
@@ -1023,6 +1029,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height,
             @NonNull Config config, boolean hasAlpha) {
         return createBitmap(null, width, height, config, hasAlpha);
@@ -1050,6 +1057,7 @@
      *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
      *         the color space is null
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height, @NonNull Config config,
             boolean hasAlpha, @NonNull ColorSpace colorSpace) {
         return createBitmap(null, width, height, config, hasAlpha, colorSpace);
@@ -1073,6 +1081,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
             @NonNull Config config, boolean hasAlpha) {
         return createBitmap(display, width, height, config, hasAlpha,
@@ -1105,6 +1114,7 @@
      *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
      *         the color space is null
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
             @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
         if (width <= 0 || height <= 0) {
@@ -1152,6 +1162,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,
             int width, int height, @NonNull Config config) {
         return createBitmap(null, colors, offset, stride, width, height, config);
@@ -1179,6 +1190,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull DisplayMetrics display,
             @NonNull @ColorInt int[] colors, int offset, int stride,
             int width, int height, @NonNull Config config) {
@@ -1221,6 +1233,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull @ColorInt int[] colors,
             int width, int height, Config config) {
         return createBitmap(null, colors, 0, width, width, height, config);
@@ -1245,6 +1258,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display,
             @NonNull @ColorInt int colors[], int width, int height, @NonNull Config config) {
         return createBitmap(display, colors, 0, width, width, height, config);
@@ -1262,7 +1276,8 @@
      * @return An immutable bitmap with a HARDWARE config whose contents are created
      * from the recorded drawing commands in the Picture source.
      */
-    public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+    @NonNull
+    public static Bitmap createBitmap(@NonNull Picture source) {
         return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
     }
 
@@ -1283,7 +1298,8 @@
      *
      * @return An immutable bitmap with a configuration specified by the config parameter
      */
-    public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+    @NonNull
+    public static Bitmap createBitmap(@NonNull Picture source, int width, int height,
             @NonNull Config config) {
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("width & height must be > 0");
@@ -1330,6 +1346,7 @@
      * Returns an optional array of private data, used by the UI system for
      * some bitmaps. Not intended to be called by applications.
      */
+    @Nullable
     public byte[] getNinePatchChunk() {
         return mNinePatchChunk;
     }
@@ -1431,7 +1448,8 @@
      * @return true if successfully compressed to the specified stream.
      */
     @WorkerThread
-    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
+    public boolean compress(@NonNull CompressFormat format, int quality,
+                            @NonNull OutputStream stream) {
         checkRecycled("Can't compress a recycled bitmap");
         // do explicit check before calling the native method
         if (stream == null) {
@@ -1548,7 +1566,7 @@
      * Convenience for calling {@link #getScaledWidth(int)} with the target
      * density of the given {@link Canvas}.
      */
-    public int getScaledWidth(Canvas canvas) {
+    public int getScaledWidth(@NonNull Canvas canvas) {
         return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
     }
 
@@ -1556,7 +1574,7 @@
      * Convenience for calling {@link #getScaledHeight(int)} with the target
      * density of the given {@link Canvas}.
      */
-    public int getScaledHeight(Canvas canvas) {
+    public int getScaledHeight(@NonNull Canvas canvas) {
         return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
     }
 
@@ -1564,7 +1582,7 @@
      * Convenience for calling {@link #getScaledWidth(int)} with the target
      * density of the given {@link DisplayMetrics}.
      */
-    public int getScaledWidth(DisplayMetrics metrics) {
+    public int getScaledWidth(@NonNull DisplayMetrics metrics) {
         return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
     }
 
@@ -1572,7 +1590,7 @@
      * Convenience for calling {@link #getScaledHeight(int)} with the target
      * density of the given {@link DisplayMetrics}.
      */
-    public int getScaledHeight(DisplayMetrics metrics) {
+    public int getScaledHeight(@NonNull DisplayMetrics metrics) {
         return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
     }
 
@@ -1682,6 +1700,7 @@
      * If the bitmap's internal config is in one of the public formats, return
      * that config, otherwise return null.
      */
+    @NonNull
     public final Config getConfig() {
         if (mRecycled) {
             Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
@@ -1967,7 +1986,7 @@
      *         to receive the specified number of pixels.
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void getPixels(@ColorInt int[] pixels, int offset, int stride,
+    public void getPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
                           int x, int y, int width, int height) {
         checkRecycled("Can't call getPixels() on a recycled bitmap");
         checkHardware("unable to getPixels(), "
@@ -2084,7 +2103,7 @@
      * @throws ArrayIndexOutOfBoundsException if the pixels array is too small
      *         to receive the specified number of pixels.
      */
-    public void setPixels(@ColorInt int[] pixels, int offset, int stride,
+    public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
             int x, int y, int width, int height) {
         checkRecycled("Can't call setPixels() on a recycled bitmap");
         if (!isMutable()) {
@@ -2098,7 +2117,7 @@
                         x, y, width, height);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR
+    public static final @NonNull Parcelable.Creator<Bitmap> CREATOR
             = new Parcelable.Creator<Bitmap>() {
         /**
          * Rebuilds a bitmap previously stored with writeToParcel().
@@ -2134,7 +2153,7 @@
      * by the final pixel format
      * @param p    Parcel object to write the bitmap data into
      */
-    public void writeToParcel(Parcel p, int flags) {
+    public void writeToParcel(@NonNull Parcel p, int flags) {
         checkRecycled("Can't parcel a recycled bitmap");
         noteHardwareBitmapSlowCall();
         if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
@@ -2150,6 +2169,7 @@
      * @return new bitmap containing the alpha channel of the original bitmap.
      */
     @CheckResult
+    @NonNull
     public Bitmap extractAlpha() {
         return extractAlpha(null, null);
     }
@@ -2180,7 +2200,8 @@
      *         paint that is passed to the draw call.
      */
     @CheckResult
-    public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+    @NonNull
+    public Bitmap extractAlpha(@Nullable Paint paint, int[] offsetXY) {
         checkRecycled("Can't extractAlpha on a recycled bitmap");
         long nativePaint = paint != null ? paint.getNativeInstance() : 0;
         noteHardwareBitmapSlowCall();
@@ -2197,12 +2218,12 @@
      *  and pixel data as this bitmap. If any of those differ, return false.
      *  If other is null, return false.
      */
-    public boolean sameAs(Bitmap other) {
+    @WorkerThread
+    public boolean sameAs(@Nullable Bitmap other) {
+        StrictMode.noteSlowCall("sameAs compares pixel data, not expected to be fast");
         checkRecycled("Can't call sameAs on a recycled bitmap!");
-        noteHardwareBitmapSlowCall();
         if (this == other) return true;
         if (other == null) return false;
-        other.noteHardwareBitmapSlowCall();
         if (other.isRecycled()) {
             throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
         }
@@ -2247,7 +2268,8 @@
      * @throws IllegalStateException if the bitmap's config is not {@link Config#HARDWARE}
      * or if the bitmap has been recycled.
      */
-    public @NonNull HardwareBuffer getHardwareBuffer() {
+    @NonNull
+    public HardwareBuffer getHardwareBuffer() {
         checkRecycled("Can't getHardwareBuffer from a recycled bitmap");
         HardwareBuffer hardwareBuffer = mHardwareBuffer == null ? null : mHardwareBuffer.get();
         if (hardwareBuffer == null || hardwareBuffer.isClosed()) {
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7ad9aec..48aecd6 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -24,29 +24,32 @@
 import java.util.Objects;
 
 /**
- * Indicates the strategies can be used when calculating the text wrapping.
+ * Specifies the line-break strategies for text wrapping.
  *
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">the line-break property</a>
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.</p>
  */
 public final class LineBreakConfig {
 
     /**
-     * No line break style specified.
+     * No line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_NONE = 0;
 
     /**
-     * Use the least restrictive rule for line-breaking. This is usually used for short lines.
+     * The least restrictive line-break rules are used for line breaking. This
+     * setting is typically used for short lines.
      */
     public static final int LINE_BREAK_STYLE_LOOSE = 1;
 
     /**
-     * Indicate breaking text with the most comment set of line-breaking rules.
+     * The most common line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_NORMAL = 2;
 
     /**
-     * Indicates breaking text with the most strictest line-breaking rules.
+     * The most strict line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_STRICT = 3;
 
@@ -59,15 +62,17 @@
     public @interface LineBreakStyle {}
 
     /**
-     * No line break word style specified.
+     * No line-break word style is used for line breaking.
      */
     public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
 
     /**
-     * Indicates the line breaking is based on the phrased. This makes text wrapping only on
-     * meaningful words. The support of the text wrapping word style varies depending on the
-     * locales. If the locale does not support the phrase based text wrapping,
-     * there will be no effect.
+     * Line breaking is based on phrases, which results in text wrapping only on
+     * meaningful words.
+     *
+     * <p>Support for this line-break word style depends on locale. If the
+     * current locale does not support phrase-based text wrapping, this setting
+     * has no effect.</p>
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
@@ -79,7 +84,7 @@
     public @interface LineBreakWordStyle {}
 
     /**
-     * A builder for creating {@link LineBreakConfig}.
+     * A builder for creating a {@code LineBreakConfig} instance.
      */
     public static final class Builder {
         // The line break style for the LineBreakConfig.
@@ -95,16 +100,16 @@
         private boolean mAutoPhraseBreaking = false;
 
         /**
-         * Builder constructor with line break parameters.
+         * Builder constructor.
          */
         public Builder() {
         }
 
         /**
-         * Set the line break style.
+         * Sets the line-break style.
          *
-         * @param lineBreakStyle the new line break style.
-         * @return this Builder
+         * @param lineBreakStyle The new line-break style.
+         * @return This {@code Builder}.
          */
         public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
             mLineBreakStyle = lineBreakStyle;
@@ -112,10 +117,10 @@
         }
 
         /**
-         * Set the line break word style.
+         * Sets the line-break word style.
          *
-         * @param lineBreakWordStyle the new line break word style.
-         * @return this Builder
+         * @param lineBreakWordStyle The new line-break word style.
+         * @return This {@code Builder}.
          */
         public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
             mLineBreakWordStyle = lineBreakWordStyle;
@@ -123,7 +128,7 @@
         }
 
         /**
-         * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
+         * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
          *
          * @hide
          */
@@ -133,9 +138,9 @@
         }
 
         /**
-         * Build the {@link LineBreakConfig}
+         * Builds a {@link LineBreakConfig} instance.
          *
-         * @return the LineBreakConfig instance.
+         * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
             return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
@@ -143,11 +148,12 @@
     }
 
     /**
-     * Create the LineBreakConfig instance.
+     * Creates a {@code LineBreakConfig} instance with the provided line break
+     * parameters.
      *
-     * @param lineBreakStyle the line break style for text wrapping.
-     * @param lineBreakWordStyle the line break word style for text wrapping.
-     * @return the {@link LineBreakConfig} instance.
+     * @param lineBreakStyle The line-break style for text wrapping.
+     * @param lineBreakWordStyle The line-break word style for text wrapping.
+     * @return The {@code LineBreakConfig} instance.
      * @hide
      */
     public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
@@ -185,8 +191,10 @@
     private final boolean mAutoPhraseBreaking;
 
     /**
-     * Constructor with the line break parameters.
-     * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
+     * Constructor with line-break parameters.
+     *
+     * <p>Use {@link LineBreakConfig.Builder} to create the
+     * {@code LineBreakConfig} instance.</p>
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
             @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
@@ -196,18 +204,18 @@
     }
 
     /**
-     * Get the line break style.
+     * Gets the current line-break style.
      *
-     * @return The current line break style to be used for the text wrapping.
+     * @return The line-break style to be used for text wrapping.
      */
     public @LineBreakStyle int getLineBreakStyle() {
         return mLineBreakStyle;
     }
 
     /**
-     * Get the line break word style.
+     * Gets the current line-break word style.
      *
-     * @return The current line break word style to be used for the text wrapping.
+     * @return The line-break word style to be used for text wrapping.
      */
     public @LineBreakWordStyle int getLineBreakWordStyle() {
         return mLineBreakWordStyle;
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
new file mode 100644
index 0000000..96da8c9
--- /dev/null
+++ b/ktfmt_includes.txt
@@ -0,0 +1,9 @@
+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
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
rename to libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
deleted file mode 100644
index 2758704..0000000
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_icon_focused" />
-    <item android:state_enabled="false"
-          android:color="@color/tv_pip_menu_icon_disabled" />
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
deleted file mode 100644
index 4f5e63d..0000000
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
similarity index 91%
rename from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
rename to libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
index ce8640d..67467bb 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
@@ -15,5 +15,5 @@
   ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
+    <item android:color="@color/tv_window_menu_icon_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
similarity index 84%
rename from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
rename to libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
index 6cbf66f..4182bfe 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
@@ -16,6 +16,6 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_close_icon_bg_focused" />
+    <item android:color="@color/tv_window_menu_close_icon_bg_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
similarity index 77%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
index 6cbf66f..45205d2 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
@@ -16,6 +16,8 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_icon_focused" />
+    <item android:state_enabled="false"
+        android:color="@color/tv_window_menu_icon_disabled" />
+    <item android:color="@color/tv_window_menu_icon_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
similarity index 84%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
index 6cbf66f..1bd26e1 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
@@ -16,6 +16,6 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_icon_bg_focused" />
+    <item android:color="@color/tv_window_menu_icon_bg_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
similarity index 64%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
index 6cbf66f..0bcaa53 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -14,8 +13,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
-</selector>
\ No newline at end of file
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 846fdb3..7085a2c 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+    android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration">
     <item android:state_activated="true">
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/pip_menu_border_corner_radius" />
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
similarity index 73%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
copy to libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
index ce8640d..2dba37d 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -14,6 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
-</selector>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/tv_window_menu_button_radius" />
+    <solid android:color="@color/tv_window_menu_icon_bg" />
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index a112f19..d183e42 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -22,6 +22,17 @@
     android:gravity="end"
     android:background="@drawable/decor_caption_title">
     <Button
+        android:id="@+id/minimize_window"
+        android:visibility="gone"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_margin="5dp"
+        android:padding="4dp"
+        android:layout_gravity="top|end"
+        android:contentDescription="@string/maximize_button_text"
+        android:background="@drawable/decor_minimize_button_dark"
+        android:duplicateParentState="true"/>
+    <Button
         android:id="@+id/maximize_window"
         android:layout_width="32dp"
         android:layout_height="32dp"
@@ -42,4 +53,3 @@
         android:background="@drawable/decor_close_button_dark"
         android:duplicateParentState="true"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
-
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 2d50d3f..8533a59 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -64,14 +64,14 @@
                 android:layout_width="@dimen/pip_menu_button_wrapper_margin"
                 android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
 
-            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+            <com.android.wm.shell.common.TvWindowMenuActionButton
                 android:id="@+id/tv_pip_menu_fullscreen_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:src="@drawable/pip_ic_fullscreen_white"
                 android:text="@string/pip_fullscreen" />
 
-            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+            <com.android.wm.shell.common.TvWindowMenuActionButton
                 android:id="@+id/tv_pip_menu_close_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
@@ -80,14 +80,14 @@
 
             <!-- More TvPipMenuActionButtons may be added here at runtime. -->
 
-            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+            <com.android.wm.shell.common.TvWindowMenuActionButton
                 android:id="@+id/tv_pip_menu_move_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:src="@drawable/pip_ic_move_white"
                 android:text="@string/pip_move" />
 
-            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+            <com.android.wm.shell.common.TvWindowMenuActionButton
                 android:id="@+id/tv_pip_menu_expand_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
@@ -145,7 +145,7 @@
         android:layout_margin="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
-    <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+    <com.android.wm.shell.common.TvWindowMenuActionButton
         android:id="@+id/tv_pip_menu_done_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
deleted file mode 100644
index db96d8d..0000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2020 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<!-- Layout for TvPipMenuActionButton -->
-<FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/button"
-        android:layout_width="@dimen/pip_menu_button_size"
-        android:layout_height="@dimen/pip_menu_button_size"
-        android:padding="@dimen/pip_menu_button_margin"
-        android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
-        android:focusable="true">
-
-        <View android:id="@+id/background"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout_gravity="center"
-              android:duplicateParentState="true"
-              android:background="@drawable/tv_pip_button_bg"/>
-
-        <ImageView android:id="@+id/icon"
-                   android:layout_width="@dimen/pip_menu_icon_size"
-                   android:layout_height="@dimen/pip_menu_icon_size"
-                   android:layout_gravity="center"
-                   android:duplicateParentState="true"
-                   android:tint="@color/tv_pip_menu_icon" />
-</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
new file mode 100644
index 0000000..c4dbd39
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+<!-- Layout for TvWindowMenuActionButton -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/button"
+    android:layout_width="@dimen/tv_window_menu_button_size"
+    android:layout_height="@dimen/tv_window_menu_button_size"
+    android:padding="@dimen/tv_window_menu_button_margin"
+    android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
+    android:focusable="true">
+
+    <View android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:duplicateParentState="true"
+        android:background="@drawable/tv_window_button_bg"/>
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="@dimen/tv_window_menu_icon_size"
+        android:layout_height="@dimen/tv_window_menu_icon_size"
+        android:layout_gravity="center"
+        android:duplicateParentState="true"
+        android:tint="@color/tv_window_menu_icon" />
+</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 7993e03..75db421 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -23,7 +23,7 @@
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
     <string name="pip_move" msgid="158770205886688553">"Mover"</string>
     <string name="pip_expand" msgid="1051966011679297308">"Mostrar"</string>
-    <string name="pip_collapse" msgid="3903295106641385962">"Ocultar"</string>
+    <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
     <string name="pip_edu_text" msgid="3672999496647508701">" Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
     <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de imagen en imagen."</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string>
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
index 14e89f8..376cc4f 100644
--- a/libs/WindowManager/Shell/res/values-television/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -21,4 +21,7 @@
 
     <!-- Padding between PIP and keep clear areas that caused it to move. -->
     <dimen name="pip_keep_clear_area_padding">16dp</dimen>
+
+    <!-- The corner radius for PiP window. -->
+    <dimen name="pip_corner_radius">0dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 02e726f..b45b9ec 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -15,20 +15,20 @@
     limitations under the License.
 -->
 <resources>
-    <!-- The dimensions to user for picture-in-picture action buttons. -->
-    <dimen name="pip_menu_button_size">48dp</dimen>
-    <dimen name="pip_menu_button_radius">20dp</dimen>
-    <dimen name="pip_menu_icon_size">20dp</dimen>
-    <dimen name="pip_menu_button_margin">4dp</dimen>
-    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
-    <dimen name="pip_menu_border_width">4dp</dimen>
-    <integer name="pip_menu_fade_animation_duration">500</integer>
+    <!-- The dimensions to use for tv window menu action buttons. -->
+    <dimen name="tv_window_menu_button_size">48dp</dimen>
+    <dimen name="tv_window_menu_button_radius">20dp</dimen>
+    <dimen name="tv_window_menu_icon_size">20dp</dimen>
+    <dimen name="tv_window_menu_button_margin">4dp</dimen>
+    <integer name="tv_window_menu_fade_animation_duration">500</integer>
     <!-- The pip menu front border corner radius is 2dp smaller than
         the background corner radius to hide the background from
         showing through. -->
     <dimen name="pip_menu_border_corner_radius">4dp</dimen>
     <dimen name="pip_menu_background_corner_radius">6dp</dimen>
+    <dimen name="pip_menu_border_width">4dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
+    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
 
     <!-- outer space minus border width -->
     <dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index fa90fe3..3e71c10 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -15,13 +15,15 @@
   ~ limitations under the License.
   -->
 <resources>
-    <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
-    <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
-    <color name="tv_pip_menu_icon_disabled">#80868B</color>
-    <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
-    <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
-    <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
-    <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+    <color name="tv_window_menu_icon_focused">#0E0E0F</color>
+    <color name="tv_window_menu_icon_unfocused">#F8F9FA</color>
+
+    <color name="tv_window_menu_icon_disabled">#80868B</color>
+    <color name="tv_window_menu_close_icon_bg_focused">#D93025</color>
+    <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color>
+    <color name="tv_window_menu_icon_bg_focused">#E8EAED</color>
+    <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
+
     <color name="tv_pip_menu_focus_border">#E8EAED</color>
     <color name="tv_pip_menu_background">#1E232C</color>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 2a38766..679bfb9 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -197,6 +197,8 @@
     <!-- Freeform window caption strings -->
     <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
     <string name="maximize_button_text">Maximize</string>
+     <!-- Accessibility text for the minimize window button [CHAR LIMIT=NONE] -->
+     <string name="minimize_button_text">Minimize</string>
     <!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
     <string name="close_button_text">Close</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d28a68a..a8764e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -54,7 +54,10 @@
 
     /** Callback for listening task state. */
     public interface Listener {
-        /** Called when the container is ready for launching activities. */
+        /**
+         * Only called once when the surface has been created & the container is ready for
+         * launching activities.
+         */
         default void onInitialized() {}
 
         /** Called when the container can no longer launch activities. */
@@ -80,12 +83,13 @@
     private final SyncTransactionQueue mSyncQueue;
     private final TaskViewTransitions mTaskViewTransitions;
 
-    private ActivityManager.RunningTaskInfo mTaskInfo;
+    protected ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
     private SurfaceControl mTaskLeash;
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
     private boolean mSurfaceCreated;
     private boolean mIsInitialized;
+    private boolean mNotifiedForInitialized;
     private Listener mListener;
     private Executor mListenerExecutor;
     private Region mObscuredTouchRegion;
@@ -110,6 +114,13 @@
         mGuard.open("release");
     }
 
+    /**
+     * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+     */
+    public boolean isInitialized() {
+        return mIsInitialized;
+    }
+
     /** Until all users are converted, we may have mixed-use (eg. Car). */
     private boolean isUsingShellTransitions() {
         return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -269,11 +280,17 @@
             resetTaskInfo();
         });
         mGuard.close();
-        if (mListener != null && mIsInitialized) {
+        mIsInitialized = false;
+        notifyReleased();
+    }
+
+    /** Called when the {@link TaskView} has been released. */
+    protected void notifyReleased() {
+        if (mListener != null && mNotifiedForInitialized) {
             mListenerExecutor.execute(() -> {
                 mListener.onReleased();
             });
-            mIsInitialized = false;
+            mNotifiedForInitialized = false;
         }
     }
 
@@ -407,12 +424,8 @@
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         mSurfaceCreated = true;
-        if (mListener != null && !mIsInitialized) {
-            mIsInitialized = true;
-            mListenerExecutor.execute(() -> {
-                mListener.onInitialized();
-            });
-        }
+        mIsInitialized = true;
+        notifyInitialized();
         mShellExecutor.execute(() -> {
             if (mTaskToken == null) {
                 // Nothing to update, task is not yet available
@@ -430,6 +443,16 @@
         });
     }
 
+    /** Called when the {@link TaskView} is initialized. */
+    protected void notifyInitialized() {
+        if (mListener != null && !mNotifiedForInitialized) {
+            mNotifiedForInitialized = true;
+            mListenerExecutor.execute(() -> {
+                mListener.onInitialized();
+            });
+        }
+    }
+
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         if (mTaskToken == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 312af4f..ee8c414 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -22,7 +22,6 @@
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.FlingAnimation
 import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.FrameCallbackScheduler
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
 
@@ -125,12 +124,6 @@
     private var defaultFling: FlingConfig = globalDefaultFling
 
     /**
-     * FrameCallbackScheduler to use if it need custom FrameCallbackScheduler, if this is null,
-     * it will use the default FrameCallbackScheduler in the DynamicAnimation.
-     */
-    private var customScheduler: FrameCallbackScheduler? = null
-
-    /**
      * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
      * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
      * just one permanent update and end listener to the DynamicAnimations.
@@ -454,14 +447,6 @@
         this.defaultFling = defaultFling
     }
 
-    /**
-     * Set the custom FrameCallbackScheduler for all aniatmion in this animator. Set this with null for
-     * restoring to default FrameCallbackScheduler.
-     */
-    fun setCustomScheduler(scheduler: FrameCallbackScheduler) {
-        this.customScheduler = scheduler
-    }
-
     /** Starts the animations! */
     fun start() {
         startAction()
@@ -511,12 +496,9 @@
                     // springs) on this property before flinging.
                     cancel(animatedProperty)
 
-                    // Apply the custom animation scheduler if it not null
-                    val flingAnim = getFlingAnimation(animatedProperty, target)
-                    flingAnim.scheduler = customScheduler ?: flingAnim.scheduler
-
                     // Apply the configuration and start the animation.
-                    flingAnim.also { flingConfig.applyToAnimation(it) }.start()
+                    getFlingAnimation(animatedProperty, target)
+                        .also { flingConfig.applyToAnimation(it) }.start()
                 }
             }
 
@@ -529,18 +511,6 @@
                     // Apply the configuration and start the animation.
                     val springAnim = getSpringAnimation(animatedProperty, target)
 
-                    // If customScheduler is exist and has not been set to the animation,
-                    // it should set here.
-                    if (customScheduler != null &&
-                            springAnim.scheduler != customScheduler) {
-                        // Cancel the animation before set animation handler
-                        if (springAnim.isRunning) {
-                            cancel(animatedProperty)
-                        }
-                        // Apply the custom scheduler handler if it not null
-                        springAnim.scheduler = customScheduler ?: springAnim.scheduler
-                    }
-
                     // Apply the configuration and start the animation.
                     springConfig.applyToAnimation(springAnim)
                     animationStartActions.add(springAnim::start)
@@ -596,12 +566,9 @@
                                     }
                                 }
 
-                                // Apply the custom animation scheduler if it not null
-                                val springAnim = getSpringAnimation(animatedProperty, target)
-                                springAnim.scheduler = customScheduler ?: springAnim.scheduler
-
                                 // Apply the configuration and start the spring animation.
-                                springAnim.also { springConfig.applyToAnimation(it) }.start()
+                                getSpringAnimation(animatedProperty, target)
+                                    .also { springConfig.applyToAnimation(it) }.start()
                             }
                         }
                     })
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index a09aab6..572e333 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.tv;
+package com.android.wm.shell.common;
 
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -28,33 +28,32 @@
 import com.android.wm.shell.R;
 
 /**
- * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
- * (provided by the application in Pip) and media buttons.
+ * A common action button for TV window menu layouts.
  */
-public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
     private final ImageView mIconImageView;
     private final View mButtonBackgroundView;
     private final View mButtonView;
     private OnClickListener mOnClickListener;
 
-    public TvPipMenuActionButton(Context context) {
+    public TvWindowMenuActionButton(Context context) {
         this(context, null, 0, 0);
     }
 
-    public TvPipMenuActionButton(Context context, AttributeSet attrs) {
+    public TvWindowMenuActionButton(Context context, AttributeSet attrs) {
         this(context, attrs, 0, 0);
     }
 
-    public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TvPipMenuActionButton(
+    public TvWindowMenuActionButton(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         final LayoutInflater inflater = (LayoutInflater) getContext()
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.tv_pip_menu_action_button, this);
+        inflater.inflate(R.layout.tv_window_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
         mButtonView = findViewById(R.id.button);
@@ -129,20 +128,27 @@
         return mButtonView.isEnabled();
     }
 
-    void setIsCustomCloseAction(boolean isCustomCloseAction) {
+    /**
+     * Marks this button as a custom close action button.
+     * This changes the style of the action button to highlight that this action finishes the
+     * Picture-in-Picture activity.
+     *
+     * @param isCustomCloseAction sets or unsets this button as a custom close action button.
+     */
+    public void setIsCustomCloseAction(boolean isCustomCloseAction) {
         mIconImageView.setImageTintList(
                 getResources().getColorStateList(
-                        isCustomCloseAction ? R.color.tv_pip_menu_close_icon
-                                : R.color.tv_pip_menu_icon));
+                        isCustomCloseAction ? R.color.tv_window_menu_close_icon
+                                : R.color.tv_window_menu_icon));
         mButtonBackgroundView.setBackgroundTintList(getResources()
-                .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
-                        : R.color.tv_pip_menu_icon_bg));
+                .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg
+                        : R.color.tv_window_menu_icon_bg));
     }
 
     @Override
     public String toString() {
         if (mButtonView.getContentDescription() == null) {
-            return TvPipMenuActionButton.class.getSimpleName();
+            return TvWindowMenuActionButton.class.getSimpleName();
         }
         return mButtonView.getContentDescription().toString();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index af205ed..a1e9f93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -92,6 +92,12 @@
     }
 
     @Override
+    public void startMinimizedModeTransition(WindowContainerTransaction wct) {
+        final int type = WindowManager.TRANSIT_TO_BACK;
+        mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+    }
+
+    @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT,
@@ -121,6 +127,8 @@
                             transition, info.getType(), change, startT, finishT);
                     break;
                 case WindowManager.TRANSIT_TO_BACK:
+                    transitionHandled |= startMinimizeTransition(transition);
+                    break;
                 case WindowManager.TRANSIT_TO_FRONT:
                     break;
             }
@@ -169,6 +177,13 @@
         return false;
     }
 
+    private boolean startMinimizeTransition(IBinder transition) {
+        if (!mPendingTransitionTokens.contains(transition)) {
+            return false;
+        }
+        return true;
+    }
+
     private boolean startChangeTransition(
             IBinder transition,
             int type,
@@ -243,4 +258,5 @@
                 return null;
         }
     }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index 25eaa0e..c947cf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -29,6 +29,15 @@
      *
      * @param targetWindowingMode the target windowing mode
      * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+     *
      */
     void startWindowingModeTransition(int targetWindowingMode, WindowContainerTransaction wct);
-}
+
+    /**
+     * Starts window minimization transition
+     *
+     * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+     *
+     */
+    void startMinimizedModeTransition(WindowContainerTransaction wct);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 81e49f8..b32c3ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -29,7 +29,6 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.graphics.Rect;
-import android.view.Choreographer;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TaskSnapshot;
@@ -279,14 +278,15 @@
             mEndValue = endValue;
             addListener(this);
             addUpdateListener(this);
-            mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+            mSurfaceControlTransactionFactory =
+                    new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
             mTransitionDirection = TRANSITION_DIRECTION_NONE;
         }
 
         @Override
         public void onAnimationStart(Animator animation) {
             mCurrentValue = mStartValue;
-            onStartTransaction(mLeash, newSurfaceControlTransaction());
+            onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
             if (mPipAnimationCallback != null) {
                 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
             }
@@ -294,14 +294,16 @@
 
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
-            applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
+            applySurfaceControlTransaction(mLeash,
+                    mSurfaceControlTransactionFactory.getTransaction(),
                     animation.getAnimatedFraction());
         }
 
         @Override
         public void onAnimationEnd(Animator animation) {
             mCurrentValue = mEndValue;
-            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
             onEndTransaction(mLeash, tx, mTransitionDirection);
             if (mPipAnimationCallback != null) {
                 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
@@ -348,7 +350,8 @@
         }
 
         void setColorContentOverlay(Context context) {
-            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
             if (mContentOverlay != null) {
                 mContentOverlay.detach(tx);
             }
@@ -357,7 +360,8 @@
         }
 
         void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
-            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
             if (mContentOverlay != null) {
                 mContentOverlay.detach(tx);
             }
@@ -406,7 +410,7 @@
         void setDestinationBounds(Rect destinationBounds) {
             mDestinationBounds.set(destinationBounds);
             if (mAnimationType == ANIM_TYPE_ALPHA) {
-                onStartTransaction(mLeash, newSurfaceControlTransaction());
+                onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
             }
         }
 
@@ -441,16 +445,6 @@
             mEndValue = endValue;
         }
 
-        /**
-         * @return {@link SurfaceControl.Transaction} instance with vsync-id.
-         */
-        protected SurfaceControl.Transaction newSurfaceControlTransaction() {
-            final SurfaceControl.Transaction tx =
-                    mSurfaceControlTransactionFactory.getTransaction();
-            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-            return tx;
-        }
-
         @VisibleForTesting
         public void setSurfaceControlTransactionFactory(
                 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3ac08a6..b9746e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -20,6 +20,7 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.view.Choreographer;
 import android.view.SurfaceControl;
 
 import com.android.wm.shell.R;
@@ -234,4 +235,18 @@
     public interface SurfaceControlTransactionFactory {
         SurfaceControl.Transaction getTransaction();
     }
+
+    /**
+     * Implementation of {@link SurfaceControlTransactionFactory} that returns
+     * {@link SurfaceControl.Transaction} with VsyncId being set.
+     */
+    public static class VsyncSurfaceControlTransactionFactory
+            implements SurfaceControlTransactionFactory {
+        @Override
+        public SurfaceControl.Transaction getTransaction() {
+            final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+            tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+            return tx;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f747b5e..b46eff6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -304,7 +304,8 @@
         mSurfaceTransactionHelper = surfaceTransactionHelper;
         mPipAnimationController = pipAnimationController;
         mPipUiEventLoggerLogger = pipUiEventLogger;
-        mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+        mSurfaceControlTransactionFactory =
+                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
         mSplitScreenOptional = splitScreenOptional;
         mTaskOrganizer = shellTaskOrganizer;
         mMainExecutor = mainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 5a21e07..44d2202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -33,10 +33,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
-import android.os.Looper;
-import android.view.Choreographer;
-
-import androidx.dynamicanimation.animation.FrameCallbackScheduler;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
@@ -89,25 +85,6 @@
     /** Coordinator instance for resolving conflicts with other floating content. */
     private FloatingContentCoordinator mFloatingContentCoordinator;
 
-    private ThreadLocal<FrameCallbackScheduler> mSfSchedulerThreadLocal =
-            ThreadLocal.withInitial(() -> {
-                final Looper initialLooper = Looper.myLooper();
-                final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
-                    @Override
-                    public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
-                        // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
-                        //  transactions
-                        Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
-                    }
-
-                    @Override
-                    public boolean isCurrentThread() {
-                        return Looper.myLooper() == initialLooper;
-                    }
-                };
-                return scheduler;
-            });
-
     /**
      * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
      * using physics animations.
@@ -210,10 +187,8 @@
     }
 
     public void init() {
-        // Note: Needs to get the shell main thread sf vsync animation handler
         mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
                 mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
-        mTemporaryBoundsPhysicsAnimator.setCustomScheduler(mSfSchedulerThreadLocal.get());
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57d3a44..4d7c846 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -56,6 +56,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -79,7 +80,7 @@
 
     private final LinearLayout mActionButtonsContainer;
     private final View mMenuFrameView;
-    private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
     private final View mPipFrameView;
     private final View mPipView;
     private final TextView mEduTextView;
@@ -94,7 +95,7 @@
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
-    private final TvPipMenuActionButton mA11yDoneButton;
+    private final TvWindowMenuActionButton mA11yDoneButton;
 
     private final ScrollView mScrollView;
     private final HorizontalScrollView mHorizontalScrollView;
@@ -104,8 +105,8 @@
     private boolean mMoveMenuIsVisible;
     private boolean mButtonMenuIsVisible;
 
-    private final TvPipMenuActionButton mExpandButton;
-    private final TvPipMenuActionButton mCloseButton;
+    private final TvWindowMenuActionButton mExpandButton;
+    private final TvWindowMenuActionButton mCloseButton;
 
     private boolean mSwitchingOrientation;
 
@@ -166,7 +167,7 @@
         mResizeAnimationDuration = context.getResources().getInteger(
                 R.integer.config_pipResizeAnimationDuration);
         mPipMenuFadeAnimationDuration = context.getResources()
-                .getInteger(R.integer.pip_menu_fade_animation_duration);
+                .getInteger(R.integer.tv_window_menu_fade_animation_duration);
 
         mPipMenuOuterSpace = context.getResources()
                 .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
@@ -568,7 +569,7 @@
         if (actionsNumber > buttonsNumber) {
             // Add buttons until we have enough to display all the actions.
             while (actionsNumber > buttonsNumber) {
-                TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
+                TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
                 button.setOnClickListener(this);
 
                 mActionButtonsContainer.addView(button,
@@ -591,7 +592,7 @@
         // "Assign" actions to the buttons.
         for (int index = 0; index < actionsNumber; index++) {
             final RemoteAction action = actions.get(index);
-            final TvPipMenuActionButton button = mAdditionalButtons.get(index);
+            final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
 
             // Remove action if it matches the custom close action.
             if (PipUtils.remoteActionsMatch(action, closeAction)) {
@@ -607,7 +608,7 @@
         }
     }
 
-    private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
+    private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
             Handler mainHandler) {
         button.setVisibility(View.VISIBLE); // Ensure the button is visible.
         if (action.getContentDescription().length() > 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index b70bde3..7b498e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -246,7 +246,7 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
                     tmpControls, new Bundle());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 279d57a..9335438 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -119,6 +119,8 @@
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
 
+    private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+
     /** List of {@link Runnable} instances to run when the last active transition has finished.  */
     private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
 
@@ -242,6 +244,16 @@
         mRemoteTransitionHandler.removeFiltered(remoteTransition);
     }
 
+    /** Registers an observer on the lifecycle of transitions. */
+    public void registerObserver(@NonNull TransitionObserver observer) {
+        mObservers.add(observer);
+    }
+
+    /** Unregisters the observer. */
+    public void unregisterObserver(@NonNull TransitionObserver observer) {
+        mObservers.remove(observer);
+    }
+
     /** Boosts the process priority of remote animation player. */
     public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
         if (appThread == null) return;
@@ -407,6 +419,11 @@
                     + Arrays.toString(mActiveTransitions.stream().map(
                             activeTransition -> activeTransition.mToken).toArray()));
         }
+
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
+        }
+
         if (!info.getRootLeash().isValid()) {
             // Invalid root-leash implies that the transition is empty/no-op, so just do
             // housekeeping and return.
@@ -474,6 +491,10 @@
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionStarting(active.mToken);
+        }
+
         setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
 
         // If a handler already chose to run this animation, try delegating to it first.
@@ -546,6 +567,10 @@
                 active.mHandler.onTransitionConsumed(
                         active.mToken, abort, abort ? null : active.mFinishT);
             }
+            for (int i = 0; i < mObservers.size(); ++i) {
+                mObservers.get(i).onTransitionMerged(
+                        active.mToken, mActiveTransitions.get(0).mToken);
+            }
             return;
         }
         final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -555,6 +580,9 @@
             active.mHandler.onTransitionConsumed(
                     transition, true /* aborted */, null /* finishTransaction */);
         }
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
         // Merge all relevant transactions together
@@ -593,6 +621,9 @@
                         transition, true /* aborted */, null /* finishTransaction */);
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
+            for (int i = 0; i < mObservers.size(); ++i) {
+                mObservers.get(i).onTransitionFinished(active.mToken, true);
+            }
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -792,6 +823,52 @@
         default void setAnimScaleSetting(float scale) {}
     }
 
+    /**
+     * Interface for something that needs to know the lifecycle of some transitions, but never
+     * handles any transition by itself.
+     */
+    public interface TransitionObserver {
+        /**
+         * Called when the transition is ready to play. It may later be merged into other
+         * transitions. Note this doesn't mean this transition will be played anytime soon.
+         *
+         * @param transition the unique token of this transition
+         * @param startTransaction the transaction given to the handler to be applied before the
+         *                         transition animation. This will be applied when the transition
+         *                         handler that handles this transition starts the transition.
+         * @param finishTransaction the transaction given to the handler to be applied after the
+         *                          transition animation. The Transition system will apply it when
+         *                          finishCallback is called by the transition handler.
+         */
+        void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction);
+
+        /**
+         * Called when the transition is starting to play. It isn't called for merged transitions.
+         *
+         * @param transition the unique token of this transition
+         */
+        void onTransitionStarting(@NonNull IBinder transition);
+
+        /**
+         * Called when a transition is merged into another transition. There won't be any following
+         * lifecycle calls for the merged transition.
+         *
+         * @param merged the unique token of the transition that's merged to another one
+         * @param playing the unique token of the transition that accepts the merge
+         */
+        void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+
+        /**
+         * Called when the transition is finished. This isn't called for merged transitions.
+         *
+         * @param transition the unique token of this transition
+         * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
+         */
+        void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+    }
+
     @BinderThread
     private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 08d6c50..e7695926 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -51,7 +51,6 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-
     private FreeformTaskTransitionStarter mTransitionStarter;
 
     public CaptionWindowDecorViewModel(
@@ -168,6 +167,14 @@
                 } else {
                     mSyncQueue.queue(wct);
                 }
+            } else if (id == R.id.minimize_window) {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.reorder(mTaskToken, false);
+                if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+                    mTransitionStarter.startMinimizedModeTransition(wct);
+                } else {
+                    mSyncQueue.queue(wct);
+                }
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index dc212fc..98b5ee9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -161,12 +161,13 @@
      */
     private void setupRootView() {
         View caption = mResult.mRootView.findViewById(R.id.caption);
-
         caption.setOnTouchListener(mOnCaptionTouchListener);
         View maximize = caption.findViewById(R.id.maximize_window);
         maximize.setOnClickListener(mOnCaptionButtonClickListener);
         View close = caption.findViewById(R.id.close_window);
         close.setOnClickListener(mOnCaptionButtonClickListener);
+        View minimize = caption.findViewById(R.id.minimize_window);
+        minimize.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
     void setCaptionColor(int captionColor) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 1c587a2..0a54b8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -49,7 +49,10 @@
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+            WindowManagerStateHelper(
+                instrumentation,
+                clearCacheAfterParsing = false
+            ).currentState.wmState.isTablet
         )
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 1e4d23c..330c9c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -20,6 +20,7 @@
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.common.region.Region
 
@@ -83,23 +84,26 @@
     }
 }
 
+fun FlickerTestParameter.layerKeepVisible(
+    component: IComponentMatcher
+) {
+    assertLayers {
+        this.isVisible(component)
+    }
+}
+
 fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
     component: IComponentMatcher,
     splitLeftTop: Boolean
 ) {
     assertLayers {
-        val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
         this.isInvisible(component)
             .then()
-            .invoke("splitAppLayerBoundsBecomesVisible") {
-                it.visibleRegion(component).coversAtMost(
-                    if (splitLeftTop) {
-                        getSplitLeftTopRegion(dividerRegion, endRotation)
-                    } else {
-                        getSplitRightBottomRegion(dividerRegion, endRotation)
-                    }
-                )
-            }
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+            .isVisible(component)
+            .then()
+            .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+            .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
     }
 }
 
@@ -108,16 +112,7 @@
     splitLeftTop: Boolean
 ) {
     assertLayers {
-        val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        this.invoke("splitAppLayerBoundsBecomesVisible") {
-                it.visibleRegion(component).coversAtMost(
-                    if (splitLeftTop) {
-                        getSplitLeftTopRegion(dividerRegion, endRotation)
-                    } else {
-                        getSplitRightBottomRegion(dividerRegion, endRotation)
-                    }
-                )
-            }
+        this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
             .then()
             .isVisible(component, true)
             .then()
@@ -141,6 +136,49 @@
     }
 }
 
+fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+    component: IComponentMatcher,
+    splitLeftTop: Boolean
+) {
+    assertLayers {
+        this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+    }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsChanges(
+    component: IComponentMatcher,
+    splitLeftTop: Boolean
+) {
+    assertLayers {
+        if (splitLeftTop) {
+            this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+                .then()
+                .isInvisible(component)
+                .then()
+                .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+        } else {
+            this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+        }
+    }
+}
+
+fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
+    component: IComponentMatcher,
+    splitLeftTop: Boolean,
+    rotation: Int
+): LayersTraceSubject {
+    return invoke("splitAppLayerBoundsSnapToDivider") {
+        val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+        it.visibleRegion(component).coversAtMost(
+            if (splitLeftTop) {
+                getSplitLeftTopRegion(dividerRegion, rotation)
+            } else {
+                getSplitRightBottomRegion(dividerRegion, rotation)
+            }
+        )
+    }
+}
+
 fun FlickerTestParameter.appWindowBecomesVisible(
     component: IComponentMatcher
 ) {
@@ -169,6 +207,14 @@
     }
 }
 
+fun FlickerTestParameter.appWindowKeepVisible(
+        component: IComponentMatcher
+) {
+    assertWm {
+        this.isAppWindowVisible(component)
+    }
+}
+
 fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
     assertLayersEnd {
         this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 3708e5f..8b717a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -23,3 +23,4 @@
 val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentMatcher("", "AppPairSplitDivider#")
 val DOCKED_STACK_DIVIDER_COMPONENT = ComponentMatcher("", "DockedStackDivider#")
 val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentMatcher("", "SplitDecorManager#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 4877442..a1226e68 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -30,6 +30,7 @@
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.testapp.Components
 
@@ -46,6 +47,7 @@
         const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
         const val DIVIDER_BAR = "docked_divider_handle"
         const val GESTURE_STEP_MS = 16L
+        const val LONG_PRESS_TIME_MS = 100L
 
         private val notificationScrollerSelector: BySelector
             get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
@@ -82,6 +84,13 @@
                 Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
             )
 
+        fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
+            SplitScreenHelper(
+                instrumentation,
+                Components.ImeActivity.LABEL,
+                Components.ImeActivity.COMPONENT.toFlickerComponent()
+            )
+
         fun waitForSplitComplete(
             wmHelper: WindowManagerStateHelper,
             primaryApp: IComponentMatcher,
@@ -206,6 +215,16 @@
             }
         }
 
+        fun longPress(
+            instrumentation: Instrumentation,
+            point: Point
+        ) {
+            val downTime = SystemClock.uptimeMillis()
+            touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+            SystemClock.sleep(LONG_PRESS_TIME_MS)
+            touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+        }
+
         fun createShortcutOnHotseatIfNotExist(
             tapl: LauncherInstrumentation,
             appName: String
@@ -220,6 +239,23 @@
             }
         }
 
+        fun dragDividerToResizeAndWait(
+            device: UiDevice,
+            wmHelper: WindowManagerStateHelper
+        ) {
+            val displayBounds = wmHelper.currentState.layerState
+                .displays.firstOrNull { !it.isVirtual }
+                ?.layerStackSpace
+                ?: error("Display not found")
+            val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+            dividerBar.drag(Point(displayBounds.width * 2 / 3, displayBounds.height * 2 / 3))
+
+            wmHelper.StateSyncBuilder()
+                .withAppTransitionIdle()
+                .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+                .waitForAndVerify()
+        }
+
         fun dragDividerToDismissSplit(
             device: UiDevice,
             wmHelper: WindowManagerStateHelper
@@ -240,5 +276,33 @@
             SystemClock.sleep(interval.toLong())
             dividerBar.click()
         }
+
+        fun copyContentFromLeftToRight(
+            instrumentation: Instrumentation,
+            device: UiDevice,
+            sourceApp: IComponentMatcher,
+            destinationApp: IComponentMatcher,
+        ) {
+            // Copy text from sourceApp
+            val textView = device.wait(Until.findObject(
+                By.res(sourceApp.packageNames.firstOrNull(), "SplitScreenTest")), TIMEOUT_MS)
+            longPress(instrumentation, textView.getVisibleCenter())
+
+            val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+            copyBtn.click()
+
+            // Paste text to destinationApp
+            val editText = device.wait(Until.findObject(
+                By.res(destinationApp.packageNames.firstOrNull(), "plain_text_input")), TIMEOUT_MS)
+            longPress(instrumentation, editText.getVisibleCenter())
+
+            val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+            pasteBtn.click()
+
+            // Verify text
+            if (!textView.getText().contentEquals(editText.getText())) {
+                error("Fail to copy content in split")
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
new file mode 100644
index 0000000..f69107e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test copy content from the left to the right side of the split-screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+    protected val textEditApp = SplitScreenHelper.getIme(instrumentation)
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    textEditApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(primaryApp.appName)
+                        .dragToSplitscreen(primaryApp.`package`, textEditApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, textEditApp, primaryApp)
+                }
+            }
+            transitions {
+                SplitScreenHelper.copyContentFromLeftToRight(
+                    instrumentation, device, primaryApp, textEditApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+        primaryApp, splitLeftTop = true)
+
+    @Presubmit
+    @Test
+    fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+        textEditApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @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
new file mode 100644
index 0000000..0f4d98d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test resize split by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    tapl.goHome()
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                }
+            }
+            transitions {
+                SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerVisibilityChanges() {
+        testSpec.assertLayers {
+            this.isVisible(secondaryApp)
+                .then()
+                .isInvisible(secondaryApp)
+                .then()
+                .isVisible(secondaryApp)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+        primaryApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+        secondaryApp, splitLeftTop = true)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @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 =
+                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 38279a3..bdfd9c7 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
@@ -29,6 +29,7 @@
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.Assume
 import org.junit.Before
@@ -80,11 +81,7 @@
 
     @Presubmit
     @Test
-    fun splitScreenDividerKeepVisible() {
-        testSpec.assertLayers {
-            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-        }
-    }
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
     @Presubmit
     @Test
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
new file mode 100644
index 0000000..da954d9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch to split pair from another app.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+    val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+                    thirdApp.launchViaIntent(wmHelper)
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withWindowSurfaceAppeared(thirdApp)
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        secondaryApp, splitLeftTop = true)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @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 c48f3f7..db89ff5 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
@@ -26,11 +26,8 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import org.junit.Assume
@@ -42,7 +39,7 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test switch back to split pair after go home
+ * Test quick switch to split pair from home.
  *
  * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
  */
@@ -60,30 +57,30 @@
     }
 
     override val transition: FlickerBuilder.() -> Unit
-    get() = {
-        super.transition(this)
-        setup {
-            eachRun {
-                primaryApp.launchViaIntent(wmHelper)
-                // TODO(b/231399940): Use recent shortcut to enter split.
-                tapl.launchedAppState.taskbar
-                    .openAllApps()
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
 
-                tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withAppTransitionIdle()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                    tapl.goHome()
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.workspace.quickSwitchToPreviousApp()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
-        transitions {
-            tapl.workspace.quickSwitchToPreviousApp()
-            SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-        }
-    }
 
     @Presubmit
     @Test
@@ -91,7 +88,7 @@
 
     @Presubmit
     @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
@@ -104,12 +101,12 @@
 
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, splitLeftTop = true)
 
     @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
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
new file mode 100644
index 0000000..c23cdb6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+                    tapl.goHome()
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.workspace.switchToOverview()
+                    .currentTask
+                    .open()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        secondaryApp, splitLeftTop = true)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @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/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
index 84789f5..642a08b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -26,6 +26,7 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:gravity="center_vertical|center_horizontal"
+        android:textIsSelectable="true"
         android:text="PrimaryActivity"
         android:textAppearance="?android:attr/textAppearanceLarge"/>
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 32f1587..ff1d2990 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -169,6 +169,7 @@
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        assertThat(mTaskView.isInitialized()).isTrue();
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
 
@@ -178,6 +179,7 @@
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         // No task, no visibility change
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
@@ -189,6 +191,7 @@
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
     }
 
@@ -223,6 +226,7 @@
 
         verify(mOrganizer).removeListener(eq(mTaskView));
         verify(mViewListener).onReleased();
+        assertThat(mTaskView.isInitialized()).isFalse();
     }
 
     @Test
@@ -270,6 +274,7 @@
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         verify(mViewListener, never()).onInitialized();
+        assertThat(mTaskView.isInitialized()).isFalse();
         // If there's no surface the task should be made invisible
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
     }
@@ -281,6 +286,7 @@
         verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean());
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         // No task, no visibility change
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
@@ -353,6 +359,7 @@
 
         verify(mOrganizer).removeListener(eq(mTaskView));
         verify(mViewListener).onReleased();
+        assertThat(mTaskView.isInitialized()).isFalse();
         verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 388792b..b142039 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -43,11 +43,13 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -59,8 +61,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.view.IDisplayWindowListener;
-import android.view.IWindowManager;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -82,6 +82,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
@@ -89,6 +90,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 
 import java.util.ArrayList;
 
@@ -688,6 +690,204 @@
         verify(runnable4, times(1)).run();
     }
 
+    @Test
+    public void testObserverLifecycle_basicTransitionFlow() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken, info, startT, finishT);
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionReady(transitToken, info, startT, finishT);
+        observerOrder.verify(observer).onTransitionStarting(transitToken);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken), anyBoolean());
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        verify(observer).onTransitionFinished(transitToken, false);
+    }
+
+    @Test
+    public void testObserverLifecycle_queueing() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken1 = new Binder();
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+        verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+        IBinder transitToken2 = new Binder();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+        verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        // first transition finished
+        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+        verify(observer, times(1)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        verify(observer, times(1)).onTransitionFinished(transitToken2, false);
+    }
+
+
+    @Test
+    public void testObserverLifecycle_merging() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        mDefaultHandler.setSimulateMerge(true);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken1 = new Binder();
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+        IBinder transitToken2 = new Binder();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionReady(transitToken2, info2, startT2, finishT2);
+        observerOrder.verify(observer).onTransitionMerged(transitToken2, transitToken1);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        // transition + merged all finished.
+        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+        // Merged transition won't receive any lifecycle calls beyond ready
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+    }
+
+    @Test
+    public void testObserverLifecycle_mergingAfterQueueing() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        mDefaultHandler.setSimulateMerge(true);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        // Make a test handler that only responds to multi-window triggers AND only animates
+        // Change transitions.
+        final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+        TestTransitionHandler testHandler = new TestTransitionHandler() {
+            @Override
+            public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                    @NonNull SurfaceControl.Transaction startTransaction,
+                    @NonNull SurfaceControl.Transaction finishTransaction,
+                    @NonNull Transitions.TransitionFinishCallback finishCallback) {
+                for (TransitionInfo.Change chg : info.getChanges()) {
+                    if (chg.getMode() == TRANSIT_CHANGE) {
+                        return super.startAnimation(transition, info, startTransaction,
+                                finishTransaction, finishCallback);
+                    }
+                }
+                return false;
+            }
+
+            @Nullable
+            @Override
+            public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+                    @NonNull TransitionRequestInfo request) {
+                final RunningTaskInfo task = request.getTriggerTask();
+                return (task != null && task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
+                        ? handlerWCT : null;
+            }
+        };
+        transitions.addHandler(testHandler);
+
+        // Use test handler to play an animation
+        IBinder transitToken1 = new Binder();
+        RunningTaskInfo mwTaskInfo =
+                createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
+        TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(TRANSIT_CHANGE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
+
+        // Request the second transition that should be handled by the default handler
+        IBinder transitToken2 = new Binder();
+        TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
+        verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+
+        // Request the third transition that should be merged into the second one
+        IBinder transitToken3 = new Binder();
+        transitions.requestStartTransition(transitToken3,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
+        verify(observer, times(0)).onTransitionStarting(transitToken3);
+
+        testHandler.finishAll();
+        mMainExecutor.flushAll();
+
+        verify(observer).onTransitionFinished(transitToken1, false);
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionStarting(transitToken2);
+        observerOrder.verify(observer).onTransitionMerged(transitToken3, transitToken2);
+        observerOrder.verify(observer).onTransitionFinished(transitToken2, false);
+
+        // Merged transition won't receive any lifecycle calls beyond ready
+        verify(observer, times(0)).onTransitionStarting(transitToken3);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
+    }
+
     class TransitionInfoBuilder {
         final TransitionInfo mInfo;
 
@@ -834,16 +1034,13 @@
     }
 
     private DisplayController createTestDisplayController() {
-        IWindowManager mockWM = mock(IWindowManager.class);
-        final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
-        try {
-            doReturn(new int[]{DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
-        } catch (RemoteException e) {
-            // No remote stuff happening, so this can't be hit
-        }
-        ShellInit shellInit = new ShellInit(mMainExecutor);
-        DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
-        shellInit.init();
+        DisplayLayout displayLayout = mock(DisplayLayout.class);
+        doReturn(Surface.ROTATION_180).when(displayLayout).getUpsideDownRotation();
+        // By default we ignore nav bar in deciding if a seamless rotation is allowed.
+        doReturn(true).when(displayLayout).allowSeamlessRotationDespiteNavBarMoving();
+
+        DisplayController out = mock(DisplayController.class);
+        doReturn(displayLayout).when(out).getDisplayLayout(DEFAULT_DISPLAY);
         return out;
     }
 
@@ -854,17 +1051,4 @@
         shellInit.init();
         return t;
     }
-//
-//    private class TestDisplayController extends DisplayController {
-//        private final DisplayLayout mTestDisplayLayout;
-//        TestDisplayController() {
-//            super(mContext, mock(IWindowManager.class), mMainExecutor);
-//            mTestDisplayLayout = new DisplayLayout();
-//            mTestDisplayLayout.
-//        }
-//
-//        @Override
-//        DisplayLayout
-//    }
-
 }
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 3a8e559..687e4dd 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -179,7 +179,7 @@
 void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
     int fast_i = (mNumFastRects - 1) * 4;
     int janky_i = (mNumJankyRects - 1) * 4;
-    ;
+
     for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
         if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
             continue;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 5a9d250..9a4bda2 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -127,7 +127,7 @@
 Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
                                        int weight, int italic) {
     Typeface* result = new Typeface;
-    result->fFontCollection.reset(new minikin::FontCollection(families));
+    result->fFontCollection = minikin::FontCollection::create(families);
 
     if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
         int weightFromFont;
@@ -191,8 +191,8 @@
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
-    std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
-            std::make_shared<minikin::FontFamily>(std::move(fonts)));
+    std::shared_ptr<minikin::FontCollection> collection =
+            minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
 
     Typeface* hwTypeface = new Typeface();
     hwTypeface->fFontCollection = collection;
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index acc1b04..c146ada 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@
     if (builder->fonts.empty()) {
         return 0;
     }
-    std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
-            builder->langId, builder->variant, std::move(builder->fonts),
-            true /* isCustomFallback */);
+    std::shared_ptr<minikin::FontFamily> family =
+            minikin::FontFamily::create(builder->langId, builder->variant,
+                                        std::move(builder->fonts), true /* isCustomFallback */);
     if (family->getCoverage().length() == 0) {
         return 0;
     }
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index b682135..fbfc07e 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -66,9 +66,9 @@
         ScopedUtfChars str(env, langTags);
         localeId = minikin::registerLocaleList(str.c_str());
     }
-    std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
-            localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
-            isCustomFallback);
+    std::shared_ptr<minikin::FontFamily> family =
+            minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant),
+                                        std::move(builder->fonts), isCustomFallback);
     if (family->getCoverage().length() == 0) {
         // No coverage means minikin rejected given font for some reasons.
         jniThrowException(env, "java/lang/IllegalArgumentException",
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2aca41e..62e42b8 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -112,7 +112,7 @@
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
         SkCanvas* profileCanvas = surface->getCanvas();
-        SkiaProfileRenderer profileRenderer(profileCanvas);
+        SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
index 492c39f..81cfc5d 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
@@ -33,13 +33,5 @@
     }
 }
 
-uint32_t SkiaProfileRenderer::getViewportWidth() {
-    return mCanvas->imageInfo().width();
-}
-
-uint32_t SkiaProfileRenderer::getViewportHeight() {
-    return mCanvas->imageInfo().height();
-}
-
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
index dc8420f..96d2a5e 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
@@ -23,18 +23,21 @@
 
 class SkiaProfileRenderer : public IProfileRenderer {
 public:
-    explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {}
+    explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height)
+            : mCanvas(canvas), mWidth(width), mHeight(height) {}
 
     void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
     void drawRects(const float* rects, int count, const SkPaint& paint) override;
-    uint32_t getViewportWidth() override;
-    uint32_t getViewportHeight() override;
+    uint32_t getViewportWidth() override { return mWidth; }
+    uint32_t getViewportHeight() override { return mHeight; }
 
     virtual ~SkiaProfileRenderer() {}
 
 private:
     // Does not have ownership.
     SkCanvas* mCanvas;
+    uint32_t mWidth;
+    uint32_t mHeight;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 905d46e..53a4c60 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -88,7 +88,9 @@
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
         SkCanvas* profileCanvas = backBuffer->getCanvas();
-        SkiaProfileRenderer profileRenderer(profileCanvas);
+        SkAutoCanvasRestore saver(profileCanvas, true);
+        profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+        SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
     }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 976117b..75d3ff7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -512,9 +512,19 @@
 
     ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
 
-    const auto drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                                  &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                                  mLightInfo, mRenderNodes, &(profiler()));
+    IRenderPipeline::DrawResult drawResult;
+    {
+        // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
+        // or it can lead to memory corruption.
+        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
+        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
+        // the thread we're primarily concerned about being responsive, this being too broad
+        // shouldn't pose a performance issue.
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
+                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
+                                           mLightInfo, mRenderNodes, &(profiler()));
+    }
 
     uint64_t frameCompleteNr = getFrameNumber();
 
@@ -754,11 +764,11 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
+        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
                 gpuCompleteTime, frameInfo->get(FrameInfoIndex::CommandSubmissionCompleted));
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
                                            surfaceControlId);
     }
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 9295a93..25cc8ca 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -64,7 +64,7 @@
                                               std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
-    return std::make_shared<minikin::FontFamily>(std::move(fonts));
+    return minikin::FontFamily::create(std::move(fonts));
 }
 
 std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) {
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 55ee3aa..f5a9850 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -47,6 +47,15 @@
  * <p>All locations generated through {@link LocationManager} are guaranteed to have a valid
  * latitude, longitude, timestamp (both Unix epoch time and elapsed realtime since boot), and
  * accuracy. All other parameters are optional.
+ *
+ * <p class="note">Note that Android provides the ability for applications to submit "mock" or faked
+ * locations through {@link LocationManager}, and that these locations can then be received by
+ * applications using LocationManager to obtain location information. These locations can be
+ * identified via the {@link #isMock()} API. Applications that wish to determine if a given location
+ * represents the best estimate of the real position of the device as opposed to a fake location
+ * coming from another application or the user should use this API. Keep in mind that the user may
+ * have a good reason for mocking their location, and thus apps should generally reject mock
+ * locations only when it is essential to their use case that only real locations are accepted.
  */
 public class Location implements Parcelable {
 
diff --git a/lowpan/java/Android.bp b/lowpan/java/Android.bp
deleted file mode 100644
index 58513d7..0000000
--- a/lowpan/java/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
-    name: "framework-lowpan-sources",
-    srcs: [
-        "**/*.java",
-        "**/*.aidl",
-    ],
-    visibility: ["//frameworks/base"],
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
deleted file mode 100644
index f09dbe3..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-interface ILowpanEnergyScanCallback {
-    oneway void onEnergyScanResult(int channel, int rssi);
-    oneway void onEnergyScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
deleted file mode 100644
index 603dc3c..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.ILowpanEnergyScanCallback;
-import android.net.lowpan.ILowpanInterfaceListener;
-import android.net.lowpan.ILowpanNetScanCallback;
-import android.net.lowpan.LowpanBeaconInfo;
-import android.net.lowpan.LowpanChannelInfo;
-import android.net.lowpan.LowpanCredential;
-import android.net.lowpan.LowpanIdentity;
-import android.net.lowpan.LowpanProvision;
-
-/** {@hide} */
-interface ILowpanInterface {
-
-    // These are here for the sake of C++ interface implementations.
-
-    const String PERM_ACCESS_LOWPAN_STATE    = "android.permission.ACCESS_LOWPAN_STATE";
-    const String PERM_CHANGE_LOWPAN_STATE    = "android.permission.CHANGE_LOWPAN_STATE";
-    const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
-
-    /**
-     * Channel mask key.
-     * Used for setting a channel mask when starting a scan.
-     * Type: int[]
-     * */
-    const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
-
-    /**
-     * Max Transmit Power Key.
-     * Used for setting the maximum transmit power when starting a network scan.
-     * Type: Integer
-     * */
-    const String KEY_MAX_TX_POWER       = "android.net.lowpan.property.MAX_TX_POWER";
-
-    // Interface States
-
-    const String STATE_OFFLINE = "offline";
-    const String STATE_COMMISSIONING = "commissioning";
-    const String STATE_ATTACHING = "attaching";
-    const String STATE_ATTACHED = "attached";
-    const String STATE_FAULT = "fault";
-
-    // Device Roles
-
-    const String ROLE_END_DEVICE = "end-device";
-    const String ROLE_ROUTER = "router";
-    const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
-    const String ROLE_SLEEPY_ROUTER = "sleepy-router";
-    const String ROLE_LEADER = "leader";
-    const String ROLE_COORDINATOR = "coordinator";
-    const String ROLE_DETACHED = "detached";
-
-    const String NETWORK_TYPE_UNKNOWN = "unknown";
-
-    /**
-     * Network type for Thread 1.x networks.
-     *
-     * @see android.net.lowpan.LowpanIdentity#getType
-     * @see #getLowpanIdentity
-     */
-    const String NETWORK_TYPE_THREAD_V1 = "org.threadgroup.thread.v1";
-
-    // Service-Specific Error Code Constants
-
-    const int ERROR_UNSPECIFIED = 1;
-    const int ERROR_INVALID_ARGUMENT = 2;
-    const int ERROR_DISABLED = 3;
-    const int ERROR_WRONG_STATE = 4;
-    const int ERROR_TIMEOUT = 5;
-    const int ERROR_IO_FAILURE = 6;
-    const int ERROR_NCP_PROBLEM = 7;
-    const int ERROR_BUSY = 8;
-    const int ERROR_ALREADY = 9;
-    const int ERROR_CANCELED = 10;
-    const int ERROR_FEATURE_NOT_SUPPORTED = 11;
-    const int ERROR_JOIN_FAILED_UNKNOWN = 12;
-    const int ERROR_JOIN_FAILED_AT_SCAN = 13;
-    const int ERROR_JOIN_FAILED_AT_AUTH = 14;
-    const int ERROR_FORM_FAILED_AT_SCAN = 15;
-
-    // Methods
-
-    @utf8InCpp String getName();
-
-    @utf8InCpp String getNcpVersion();
-    @utf8InCpp String getDriverVersion();
-    LowpanChannelInfo[] getSupportedChannels();
-    @utf8InCpp String[] getSupportedNetworkTypes();
-    byte[] getMacAddress();
-
-    boolean isEnabled();
-    void setEnabled(boolean enabled);
-
-    boolean isUp();
-    boolean isCommissioned();
-    boolean isConnected();
-    @utf8InCpp String getState();
-
-    @utf8InCpp String getRole();
-    @utf8InCpp String getPartitionId();
-    byte[] getExtendedAddress();
-
-    LowpanIdentity getLowpanIdentity();
-    LowpanCredential getLowpanCredential();
-
-    @utf8InCpp String[] getLinkAddresses();
-    IpPrefix[] getLinkNetworks();
-
-    void join(in LowpanProvision provision);
-    void form(in LowpanProvision provision);
-    void attach(in LowpanProvision provision);
-    void leave();
-    void reset();
-
-    void startCommissioningSession(in LowpanBeaconInfo beaconInfo);
-    void closeCommissioningSession();
-    oneway void sendToCommissioner(in byte[] packet);
-
-    void beginLowPower();
-    oneway void pollForData();
-
-    oneway void onHostWake();
-
-    void addListener(ILowpanInterfaceListener listener);
-    oneway void removeListener(ILowpanInterfaceListener listener);
-
-    void startNetScan(in Map properties, ILowpanNetScanCallback listener);
-    oneway void stopNetScan();
-
-    void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
-    oneway void stopEnergyScan();
-
-    void addOnMeshPrefix(in IpPrefix prefix, int flags);
-    oneway void removeOnMeshPrefix(in IpPrefix prefix);
-
-    void addExternalRoute(in IpPrefix prefix, int flags);
-    oneway void removeExternalRoute(in IpPrefix prefix);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
deleted file mode 100644
index 5e4049a..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.LowpanIdentity;
-
-/** {@hide} */
-interface ILowpanInterfaceListener {
-    oneway void onEnabledChanged(boolean value);
-
-    oneway void onConnectedChanged(boolean value);
-
-    oneway void onUpChanged(boolean value);
-
-    oneway void onRoleChanged(@utf8InCpp String value);
-
-    oneway void onStateChanged(@utf8InCpp String value);
-
-    oneway void onLowpanIdentityChanged(in LowpanIdentity value);
-
-    oneway void onLinkNetworkAdded(in IpPrefix value);
-
-    oneway void onLinkNetworkRemoved(in IpPrefix value);
-
-    oneway void onLinkAddressAdded(@utf8InCpp String value);
-
-    oneway void onLinkAddressRemoved(@utf8InCpp String value);
-
-    oneway void onReceiveFromCommissioner(in byte[] packet);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
deleted file mode 100644
index 326aa65..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ /dev/null
@@ -1,36 +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 android.net.lowpan;
-import android.net.lowpan.ILowpanInterface;
-import android.net.lowpan.ILowpanManagerListener;
-
-/** {@hide} */
-interface ILowpanManager {
-
-    /* Keep this in sync with Context.LOWPAN_SERVICE */
-    const String LOWPAN_SERVICE_NAME = "lowpan";
-
-    ILowpanInterface getInterface(@utf8InCpp String name);
-
-    @utf8InCpp String[] getInterfaceList();
-
-    void addListener(ILowpanManagerListener listener);
-    void removeListener(ILowpanManagerListener listener);
-
-    void addInterface(ILowpanInterface lowpan_interface);
-    void removeInterface(ILowpanInterface lowpan_interface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
deleted file mode 100644
index d4846f6..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
+++ /dev/null
@@ -1,25 +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 android.net.lowpan;
-
-import android.net.lowpan.ILowpanInterface;
-
-/** {@hide} */
-interface ILowpanManagerListener {
-    oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
-    oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
deleted file mode 100644
index 9743fce..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.lowpan.LowpanBeaconInfo;
-
-/** {@hide} */
-interface ILowpanNetScanCallback {
-    oneway void onNetScanBeacon(in LowpanBeaconInfo beacon);
-    oneway void onNetScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
deleted file mode 100644
index e917d45..0000000
--- a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation requires the interface to be enabled.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class InterfaceDisabledException extends LowpanException {
-
-    public InterfaceDisabledException() {}
-
-    public InterfaceDisabledException(String message) {
-        super(message);
-    }
-
-    public InterfaceDisabledException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected InterfaceDisabledException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
deleted file mode 100644
index 7aceb71..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtAuthException extends JoinFailedException {
-
-    public JoinFailedAtAuthException() {}
-
-    public JoinFailedAtAuthException(String message) {
-        super(message);
-    }
-
-    public JoinFailedAtAuthException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public JoinFailedAtAuthException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
deleted file mode 100644
index a4346f98..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtScanException extends JoinFailedException {
-
-    public JoinFailedAtScanException() {}
-
-    public JoinFailedAtScanException(String message) {
-        super(message);
-    }
-
-    public JoinFailedAtScanException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public JoinFailedAtScanException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedException.java b/lowpan/java/android/net/lowpan/JoinFailedException.java
deleted file mode 100644
index e51d382..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation has failed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedException extends LowpanException {
-
-    public JoinFailedException() {}
-
-    public JoinFailedException(String message) {
-        super(message);
-    }
-
-    public JoinFailedException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected JoinFailedException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
deleted file mode 100644
index 9464fea..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanBeaconInfo cpp_header "android/net/lowpan/LowpanBeaconInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
deleted file mode 100644
index 5d4a3a0..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.TreeSet;
-
-/**
- * Describes a LoWPAN Beacon
- *
- * @hide
- */
-// @SystemApi
-public class LowpanBeaconInfo implements Parcelable {
-    public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
-    public static final int UNKNOWN_LQI = 0;
-
-    private LowpanIdentity mIdentity;
-    private int mRssi = UNKNOWN_RSSI;
-    private int mLqi = UNKNOWN_LQI;
-    private byte[] mBeaconAddress = null;
-    private final TreeSet<Integer> mFlags = new TreeSet<>();
-
-    public static final int FLAG_CAN_ASSIST = 1;
-
-    /** @hide */
-    public static class Builder {
-        final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
-        final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
-
-        public Builder setLowpanIdentity(LowpanIdentity x) {
-            mIdentityBuilder.setLowpanIdentity(x);
-            return this;
-        }
-
-        public Builder setName(String x) {
-            mIdentityBuilder.setName(x);
-            return this;
-        }
-
-        public Builder setXpanid(byte x[]) {
-            mIdentityBuilder.setXpanid(x);
-            return this;
-        }
-
-        public Builder setPanid(int x) {
-            mIdentityBuilder.setPanid(x);
-            return this;
-        }
-
-        public Builder setChannel(int x) {
-            mIdentityBuilder.setChannel(x);
-            return this;
-        }
-
-        public Builder setType(String x) {
-            mIdentityBuilder.setType(x);
-            return this;
-        }
-
-        public Builder setRssi(int x) {
-            mBeaconInfo.mRssi = x;
-            return this;
-        }
-
-        public Builder setLqi(int x) {
-            mBeaconInfo.mLqi = x;
-            return this;
-        }
-
-        public Builder setBeaconAddress(byte x[]) {
-            mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
-            return this;
-        }
-
-        public Builder setFlag(int x) {
-            mBeaconInfo.mFlags.add(x);
-            return this;
-        }
-
-        public Builder setFlags(Collection<Integer> x) {
-            mBeaconInfo.mFlags.addAll(x);
-            return this;
-        }
-
-        public LowpanBeaconInfo build() {
-            mBeaconInfo.mIdentity = mIdentityBuilder.build();
-            if (mBeaconInfo.mBeaconAddress == null) {
-                mBeaconInfo.mBeaconAddress = new byte[0];
-            }
-            return mBeaconInfo;
-        }
-    }
-
-    private LowpanBeaconInfo() {}
-
-    public LowpanIdentity getLowpanIdentity() {
-        return mIdentity;
-    }
-
-    public int getRssi() {
-        return mRssi;
-    }
-
-    public int getLqi() {
-        return mLqi;
-    }
-
-    public byte[] getBeaconAddress() {
-        return mBeaconAddress.clone();
-    }
-
-    public Collection<Integer> getFlags() {
-        return (Collection<Integer>) mFlags.clone();
-    }
-
-    public boolean isFlagSet(int flag) {
-        return mFlags.contains(flag);
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append(mIdentity.toString());
-
-        if (mRssi != UNKNOWN_RSSI) {
-            sb.append(", RSSI:").append(mRssi).append("dBm");
-        }
-
-        if (mLqi != UNKNOWN_LQI) {
-            sb.append(", LQI:").append(mLqi);
-        }
-
-        if (mBeaconAddress.length > 0) {
-            sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
-        }
-
-        for (Integer flag : mFlags) {
-            switch (flag.intValue()) {
-                case FLAG_CAN_ASSIST:
-                    sb.append(", CAN_ASSIST");
-                    break;
-                default:
-                    sb.append(", FLAG_").append(Integer.toHexString(flag));
-                    break;
-            }
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanBeaconInfo)) {
-            return false;
-        }
-        LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
-        return mIdentity.equals(rhs.mIdentity)
-                && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
-                && mRssi == rhs.mRssi
-                && mLqi == rhs.mLqi
-                && mFlags.equals(rhs.mFlags);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mIdentity.writeToParcel(dest, flags);
-        dest.writeInt(mRssi);
-        dest.writeInt(mLqi);
-        dest.writeByteArray(mBeaconAddress);
-
-        dest.writeInt(mFlags.size());
-        for (Integer val : mFlags) {
-            dest.writeInt(val);
-        }
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanBeaconInfo> CREATOR =
-            new Creator<LowpanBeaconInfo>() {
-                public LowpanBeaconInfo createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
-                    builder.setRssi(in.readInt());
-                    builder.setLqi(in.readInt());
-
-                    builder.setBeaconAddress(in.createByteArray());
-
-                    for (int i = in.readInt(); i > 0; i--) {
-                        builder.setFlag(in.readInt());
-                    }
-
-                    return builder.build();
-                }
-
-                public LowpanBeaconInfo[] newArray(int size) {
-                    return new LowpanBeaconInfo[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
deleted file mode 100644
index 0676deb..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanChannelInfo cpp_header "android/net/lowpan/LowpanChannelInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
deleted file mode 100644
index 12c98b6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Provides detailed information about a given channel.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanChannelInfo implements Parcelable {
-
-    public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
-    public static final float UNKNOWN_FREQUENCY = 0.0f;
-    public static final float UNKNOWN_BANDWIDTH = 0.0f;
-
-    private int mIndex = 0;
-    private String mName = null;
-    private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
-    private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
-    private int mMaxTransmitPower = UNKNOWN_POWER;
-    private boolean mIsMaskedByRegulatoryDomain = false;
-
-    /** @hide */
-    public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
-        LowpanChannelInfo info = new LowpanChannelInfo();
-
-        if (index < 0) {
-            info = null;
-
-        } else if (index == 0) {
-            info.mSpectrumCenterFrequency = 868300000.0f;
-            info.mSpectrumBandwidth = 600000.0f;
-
-        } else if (index < 11) {
-            info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
-            info.mSpectrumBandwidth = 0; // Unknown
-
-        } else if (index < 26) {
-            info.mSpectrumCenterFrequency =
-                    2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
-            info.mSpectrumBandwidth = 2000000.0f;
-
-        } else {
-            info = null;
-        }
-
-        info.mName = Integer.toString(index);
-
-        return info;
-    }
-
-    private LowpanChannelInfo() {}
-
-    private LowpanChannelInfo(int index, String name, float cf, float bw) {
-        mIndex = index;
-        mName = name;
-        mSpectrumCenterFrequency = cf;
-        mSpectrumBandwidth = bw;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public int getIndex() {
-        return mIndex;
-    }
-
-    public int getMaxTransmitPower() {
-        return mMaxTransmitPower;
-    }
-
-    public boolean isMaskedByRegulatoryDomain() {
-        return mIsMaskedByRegulatoryDomain;
-    }
-
-    public float getSpectrumCenterFrequency() {
-        return mSpectrumCenterFrequency;
-    }
-
-    public float getSpectrumBandwidth() {
-        return mSpectrumBandwidth;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("Channel ").append(mIndex);
-
-        if (mName != null && !mName.equals(Integer.toString(mIndex))) {
-            sb.append(" (").append(mName).append(")");
-        }
-
-        if (mSpectrumCenterFrequency > 0.0f) {
-            if (mSpectrumCenterFrequency > 1000000000.0f) {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000000000.0f)
-                        .append("GHz");
-            } else if (mSpectrumCenterFrequency > 1000000.0f) {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000000.0f)
-                        .append("MHz");
-            } else {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000.0f)
-                        .append("kHz");
-            }
-        }
-
-        if (mSpectrumBandwidth > 0.0f) {
-            if (mSpectrumBandwidth > 1000000000.0f) {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000000000.0f)
-                        .append("GHz");
-            } else if (mSpectrumBandwidth > 1000000.0f) {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000000.0f)
-                        .append("MHz");
-            } else {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000.0f)
-                        .append("kHz");
-            }
-        }
-
-        if (mMaxTransmitPower != UNKNOWN_POWER) {
-            sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanChannelInfo)) {
-            return false;
-        }
-        LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
-        return Objects.equals(mName, rhs.mName)
-                && mIndex == rhs.mIndex
-                && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
-                && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
-                && mSpectrumBandwidth == rhs.mSpectrumBandwidth
-                && mMaxTransmitPower == rhs.mMaxTransmitPower;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                mName,
-                mIndex,
-                mIsMaskedByRegulatoryDomain,
-                mSpectrumCenterFrequency,
-                mSpectrumBandwidth,
-                mMaxTransmitPower);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mIndex);
-        dest.writeString(mName);
-        dest.writeFloat(mSpectrumCenterFrequency);
-        dest.writeFloat(mSpectrumBandwidth);
-        dest.writeInt(mMaxTransmitPower);
-        dest.writeBoolean(mIsMaskedByRegulatoryDomain);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanChannelInfo> CREATOR =
-            new Creator<LowpanChannelInfo>() {
-
-                public LowpanChannelInfo createFromParcel(Parcel in) {
-                    LowpanChannelInfo info = new LowpanChannelInfo();
-
-                    info.mIndex = in.readInt();
-                    info.mName = in.readString();
-                    info.mSpectrumCenterFrequency = in.readFloat();
-                    info.mSpectrumBandwidth = in.readFloat();
-                    info.mMaxTransmitPower = in.readInt();
-                    info.mIsMaskedByRegulatoryDomain = in.readBoolean();
-
-                    return info;
-                }
-
-                public LowpanChannelInfo[] newArray(int size) {
-                    return new LowpanChannelInfo[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
deleted file mode 100644
index 8f75e8d..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
-/**
- * Commissioning Session.
- *
- * <p>This class enables a device to learn the credential needed to join a network using a technique
- * called "in-band commissioning".
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCommissioningSession {
-
-    private final ILowpanInterface mBinder;
-    private final LowpanBeaconInfo mBeaconInfo;
-    private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
-    private final Looper mLooper;
-    private Handler mHandler;
-    private Callback mCallback = null;
-    private volatile boolean mIsClosed = false;
-
-    /**
-     * Callback base class for {@link LowpanCommissioningSession}
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
-
-        public void onClosed() {};
-    }
-
-    private class InternalCallback extends ILowpanInterfaceListener.Stub {
-        @Override
-        public void onStateChanged(String value) {
-            if (!mIsClosed) {
-                switch (value) {
-                    case ILowpanInterface.STATE_OFFLINE:
-                    case ILowpanInterface.STATE_FAULT:
-                        synchronized (LowpanCommissioningSession.this) {
-                            lockedCleanup();
-                        }
-                }
-            }
-        }
-
-        @Override
-        public void onReceiveFromCommissioner(byte[] packet) {
-            mHandler.post(
-                    () -> {
-                        synchronized (LowpanCommissioningSession.this) {
-                            if (!mIsClosed && (mCallback != null)) {
-                                mCallback.onReceiveFromCommissioner(packet);
-                            }
-                        }
-                    });
-        }
-
-        // We ignore all other callbacks.
-        @Override
-        public void onEnabledChanged(boolean value) {}
-
-        @Override
-        public void onConnectedChanged(boolean value) {}
-
-        @Override
-        public void onUpChanged(boolean value) {}
-
-        @Override
-        public void onRoleChanged(String value) {}
-
-        @Override
-        public void onLowpanIdentityChanged(LowpanIdentity value) {}
-
-        @Override
-        public void onLinkNetworkAdded(IpPrefix value) {}
-
-        @Override
-        public void onLinkNetworkRemoved(IpPrefix value) {}
-
-        @Override
-        public void onLinkAddressAdded(String value) {}
-
-        @Override
-        public void onLinkAddressRemoved(String value) {}
-    }
-
-    LowpanCommissioningSession(
-            ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
-        mBinder = binder;
-        mBeaconInfo = beaconInfo;
-        mLooper = looper;
-
-        if (mLooper != null) {
-            mHandler = new Handler(mLooper);
-        } else {
-            mHandler = new Handler();
-        }
-
-        try {
-            mBinder.addListener(mInternalCallback);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    private void lockedCleanup() {
-        // Note: this method is only called from synchronized contexts.
-
-        if (!mIsClosed) {
-            try {
-                mBinder.removeListener(mInternalCallback);
-
-            } catch (DeadObjectException x) {
-                /* We don't care if we receive a DOE at this point.
-                 * DOE is as good as success as far as we are concerned.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-
-            if (mCallback != null) {
-                mHandler.post(() -> mCallback.onClosed());
-            }
-        }
-
-        mCallback = null;
-        mIsClosed = true;
-    }
-
-    /** TODO: doc */
-    @NonNull
-    public LowpanBeaconInfo getBeaconInfo() {
-        return mBeaconInfo;
-    }
-
-    /** TODO: doc */
-    public void sendToCommissioner(@NonNull byte[] packet) {
-        if (!mIsClosed) {
-            try {
-                mBinder.sendToCommissioner(packet);
-
-            } catch (DeadObjectException x) {
-                /* This method is a best-effort delivery.
-                 * We don't care if we receive a DOE at this point.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-        }
-    }
-
-    /** TODO: doc */
-    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
-        if (!mIsClosed) {
-            /* This class can be created with or without a default looper.
-             * Also, this method can be called with or without a specific
-             * handler. If a handler is specified, it is to always be used.
-             * Otherwise, if there was a Looper specified when this object
-             * was created, we create a new handle based on that looper.
-             * Otherwise we just create a default handler object. Since we
-             * don't really know how the previous handler was created, we
-             * end up always replacing it here. This isn't a huge problem
-             * because this method should be called infrequently.
-             */
-            if (handler != null) {
-                mHandler = handler;
-            } else if (mLooper != null) {
-                mHandler = new Handler(mLooper);
-            } else {
-                mHandler = new Handler();
-            }
-            mCallback = cb;
-        }
-    }
-
-    /** TODO: doc */
-    public synchronized void close() {
-        if (!mIsClosed) {
-            try {
-                mBinder.closeCommissioningSession();
-
-                lockedCleanup();
-
-            } catch (DeadObjectException x) {
-                /* We don't care if we receive a DOE at this point.
-                 * DOE is as good as success as far as we are concerned.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.aidl b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
deleted file mode 100644
index af0c2d6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanCredential cpp_header "android/net/lowpan/LowpanCredential.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
deleted file mode 100644
index dcbb831..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes a credential for a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCredential implements Parcelable {
-
-    public static final int UNSPECIFIED_KEY_INDEX = 0;
-
-    private byte[] mMasterKey = null;
-    private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
-
-    LowpanCredential() {}
-
-    private LowpanCredential(byte[] masterKey, int keyIndex) {
-        setMasterKey(masterKey, keyIndex);
-    }
-
-    private LowpanCredential(byte[] masterKey) {
-        setMasterKey(masterKey);
-    }
-
-    public static LowpanCredential createMasterKey(byte[] masterKey) {
-        return new LowpanCredential(masterKey);
-    }
-
-    public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
-        return new LowpanCredential(masterKey, keyIndex);
-    }
-
-    void setMasterKey(byte[] masterKey) {
-        if (masterKey != null) {
-            masterKey = masterKey.clone();
-        }
-        mMasterKey = masterKey;
-    }
-
-    void setMasterKeyIndex(int keyIndex) {
-        mMasterKeyIndex = keyIndex;
-    }
-
-    void setMasterKey(byte[] masterKey, int keyIndex) {
-        setMasterKey(masterKey);
-        setMasterKeyIndex(keyIndex);
-    }
-
-    public byte[] getMasterKey() {
-        if (mMasterKey != null) {
-            return mMasterKey.clone();
-        }
-        return null;
-    }
-
-    public int getMasterKeyIndex() {
-        return mMasterKeyIndex;
-    }
-
-    public boolean isMasterKey() {
-        return mMasterKey != null;
-    }
-
-    public String toSensitiveString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("<LowpanCredential");
-
-        if (isMasterKey()) {
-            sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
-            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
-                sb.append(", Index:").append(mMasterKeyIndex);
-            }
-        } else {
-            sb.append(" empty");
-        }
-
-        sb.append(">");
-
-        return sb.toString();
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("<LowpanCredential");
-
-        if (isMasterKey()) {
-            // We don't print out the contents of the key here,
-            // we only do that in toSensitiveString.
-            sb.append(" MasterKey");
-            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
-                sb.append(", Index:").append(mMasterKeyIndex);
-            }
-        } else {
-            sb.append(" empty");
-        }
-
-        sb.append(">");
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanCredential)) {
-            return false;
-        }
-        LowpanCredential rhs = (LowpanCredential) obj;
-        return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByteArray(mMasterKey);
-        dest.writeInt(mMasterKeyIndex);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanCredential> CREATOR =
-            new Creator<LowpanCredential>() {
-
-                public LowpanCredential createFromParcel(Parcel in) {
-                    LowpanCredential credential = new LowpanCredential();
-
-                    credential.mMasterKey = in.createByteArray();
-                    credential.mMasterKeyIndex = in.readInt();
-
-                    return credential;
-                }
-
-                public LowpanCredential[] newArray(int size) {
-                    return new LowpanCredential[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
deleted file mode 100644
index da87752..0000000
--- a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Describes the result from one channel of an energy scan.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanEnergyScanResult {
-    public static final int UNKNOWN = Integer.MAX_VALUE;
-
-    private int mChannel = UNKNOWN;
-    private int mMaxRssi = UNKNOWN;
-
-    LowpanEnergyScanResult() {}
-
-    public int getChannel() {
-        return mChannel;
-    }
-
-    public int getMaxRssi() {
-        return mMaxRssi;
-    }
-
-    void setChannel(int x) {
-        mChannel = x;
-    }
-
-    void setMaxRssi(int x) {
-        mMaxRssi = x;
-    }
-
-    @Override
-    public String toString() {
-        return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
deleted file mode 100644
index 5dfce48..0000000
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.ServiceSpecificException;
-import android.util.AndroidException;
-
-/**
- * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
- * or a LoWPAN interface property could not be fetched or changed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanException extends AndroidException {
-    public LowpanException() {}
-
-    public LowpanException(String message) {
-        super(message);
-    }
-
-    public LowpanException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public LowpanException(Exception cause) {
-        super(cause);
-    }
-
-    /* This method returns LowpanException so that the caller
-     * can add "throw" before the invocation of this method.
-     * This might seem superfluous, but it is actually to
-     * help provide a hint to the java compiler that this
-     * function will not return.
-     */
-    static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
-            throws LowpanException {
-        switch (e.errorCode) {
-            case ILowpanInterface.ERROR_DISABLED:
-                throw new InterfaceDisabledException(e);
-
-            case ILowpanInterface.ERROR_WRONG_STATE:
-                throw new WrongStateException(e);
-
-            case ILowpanInterface.ERROR_CANCELED:
-                throw new OperationCanceledException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
-                throw new JoinFailedException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
-                throw new JoinFailedAtScanException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
-                throw new JoinFailedAtAuthException(e);
-
-            case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
-                throw new NetworkAlreadyExistsException(e);
-
-            case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
-                throw new LowpanException(
-                        e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
-
-            case ILowpanInterface.ERROR_NCP_PROBLEM:
-                throw new LowpanRuntimeException(
-                        e.getMessage() != null ? e.getMessage() : "NCP problem", e);
-
-            case ILowpanInterface.ERROR_INVALID_ARGUMENT:
-                throw new LowpanRuntimeException(
-                        e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
-
-            case ILowpanInterface.ERROR_UNSPECIFIED:
-            default:
-                throw new LowpanRuntimeException(e);
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl b/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
deleted file mode 100644
index fcef98f..0000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanIdentity cpp_header "android/net/lowpan/LowpanIdentity.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
deleted file mode 100644
index 1997bc4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.icu.text.StringPrep;
-import android.icu.text.StringPrepParseException;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-import com.android.internal.util.HexDump;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes an instance of a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanIdentity implements Parcelable {
-    private static final String TAG = LowpanIdentity.class.getSimpleName();
-
-    // Constants
-    public static final int UNSPECIFIED_CHANNEL = -1;
-    public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
-    // Builder
-
-    /** @hide */
-    // @SystemApi
-    public static class Builder {
-        private static final StringPrep stringPrep =
-                StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
-
-        final LowpanIdentity mIdentity = new LowpanIdentity();
-
-        private static String escape(@NonNull byte[] bytes) {
-            StringBuffer sb = new StringBuffer();
-            for (byte b : bytes) {
-                if (b >= 32 && b <= 126) {
-                    sb.append((char) b);
-                } else {
-                    sb.append(String.format("\\0x%02x", b & 0xFF));
-                }
-            }
-            return sb.toString();
-        }
-
-        public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
-            Objects.requireNonNull(x);
-            setRawName(x.getRawName());
-            setXpanid(x.getXpanid());
-            setPanid(x.getPanid());
-            setChannel(x.getChannel());
-            setType(x.getType());
-            return this;
-        }
-
-        public Builder setName(@NonNull String name) {
-            Objects.requireNonNull(name);
-            try {
-                mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
-                mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
-                mIdentity.mIsNameValid = true;
-            } catch (StringPrepParseException x) {
-                Log.w(TAG, x.toString());
-                setRawName(name.getBytes(StandardCharsets.UTF_8));
-            }
-            return this;
-        }
-
-        public Builder setRawName(@NonNull byte[] name) {
-            Objects.requireNonNull(name);
-            mIdentity.mRawName = name.clone();
-            mIdentity.mName = new String(name, StandardCharsets.UTF_8);
-            try {
-                String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
-                mIdentity.mIsNameValid =
-                        Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
-            } catch (StringPrepParseException x) {
-                Log.w(TAG, x.toString());
-                mIdentity.mIsNameValid = false;
-            }
-
-            // Non-normal names must be rendered differently to avoid confusion.
-            if (!mIdentity.mIsNameValid) {
-                mIdentity.mName = "«" + escape(name) + "»";
-            }
-
-            return this;
-        }
-
-        public Builder setXpanid(byte x[]) {
-            mIdentity.mXpanid = (x != null ? x.clone() : null);
-            return this;
-        }
-
-        public Builder setPanid(int x) {
-            mIdentity.mPanid = x;
-            return this;
-        }
-
-        public Builder setType(@NonNull String x) {
-            mIdentity.mType = x;
-            return this;
-        }
-
-        public Builder setChannel(int x) {
-            mIdentity.mChannel = x;
-            return this;
-        }
-
-        public LowpanIdentity build() {
-            return mIdentity;
-        }
-    }
-
-    LowpanIdentity() {}
-
-    // Instance Variables
-
-    private String mName = "";
-    private boolean mIsNameValid = true;
-    private byte[] mRawName = new byte[0];
-    private String mType = "";
-    private byte[] mXpanid = new byte[0];
-    private int mPanid = UNSPECIFIED_PANID;
-    private int mChannel = UNSPECIFIED_CHANNEL;
-
-    // Public Getters
-
-    public String getName() {
-        return mName;
-    }
-
-    public boolean isNameValid() {
-        return mIsNameValid;
-    }
-
-    public byte[] getRawName() {
-        return mRawName.clone();
-    }
-
-    public byte[] getXpanid() {
-        return mXpanid.clone();
-    }
-
-    public int getPanid() {
-        return mPanid;
-    }
-
-    public String getType() {
-        return mType;
-    }
-
-    public int getChannel() {
-        return mChannel;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("Name:").append(getName());
-
-        if (mType.length() > 0) {
-            sb.append(", Type:").append(mType);
-        }
-
-        if (mXpanid.length > 0) {
-            sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
-        }
-
-        if (mPanid != UNSPECIFIED_PANID) {
-            sb.append(", PANID:").append(String.format("0x%04X", mPanid));
-        }
-
-        if (mChannel != UNSPECIFIED_CHANNEL) {
-            sb.append(", Channel:").append(mChannel);
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanIdentity)) {
-            return false;
-        }
-        LowpanIdentity rhs = (LowpanIdentity) obj;
-        return Arrays.equals(mRawName, rhs.mRawName)
-                && Arrays.equals(mXpanid, rhs.mXpanid)
-                && mType.equals(rhs.mType)
-                && mPanid == rhs.mPanid
-                && mChannel == rhs.mChannel;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByteArray(mRawName);
-        dest.writeString(mType);
-        dest.writeByteArray(mXpanid);
-        dest.writeInt(mPanid);
-        dest.writeInt(mChannel);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanIdentity> CREATOR =
-            new Creator<LowpanIdentity>() {
-
-                public LowpanIdentity createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setRawName(in.createByteArray());
-                    builder.setType(in.readString());
-                    builder.setXpanid(in.createByteArray());
-                    builder.setPanid(in.readInt());
-                    builder.setChannel(in.readInt());
-
-                    return builder.build();
-                }
-
-                public LowpanIdentity[] newArray(int size) {
-                    return new LowpanIdentity[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
deleted file mode 100644
index 57e9135..0000000
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import java.util.HashMap;
-
-/**
- * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanInterface {
-    private static final String TAG = LowpanInterface.class.getSimpleName();
-
-    /** Detached role. The interface is not currently attached to a network. */
-    public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
-
-    /** End-device role. End devices do not route traffic for other nodes. */
-    public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
-
-    /** Router role. Routers help route traffic around the mesh network. */
-    public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
-
-    /**
-     * Sleepy End-Device role.
-     *
-     * <p>End devices with this role are nominally asleep, waking up periodically to check in with
-     * their parent to see if there are packets destined for them. Such devices are capable of
-     * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
-     * seconds(depending on how the node is configured).
-     */
-    public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
-
-    /**
-     * Sleepy-router role.
-     *
-     * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
-     * routers and their children.
-     */
-    public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
-
-    /** TODO: doc */
-    public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
-
-    /** TODO: doc */
-    public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
-
-    /**
-     * Offline state.
-     *
-     * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
-     * this state the NCP is idle and not connected to any network.
-     *
-     * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
-     * <code>setUp(false)</code>, with the later two only working if we were not previously in the
-     * {@link #STATE_FAULT} state.
-     *
-     * @see #getState()
-     * @see #STATE_FAULT
-     */
-    public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
-
-    /**
-     * Commissioning state.
-     *
-     * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
-     * state may only be entered directly from the {@link #STATE_OFFLINE} state.
-     *
-     * @see #startCommissioningSession()
-     * @see #getState()
-     * @hide
-     */
-    public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
-
-    /**
-     * Attaching state.
-     *
-     * <p>The interface enters this state when it starts the process of trying to find other nodes
-     * so that it can attach to any pre-existing network fragment, or when it is in the process of
-     * calculating the optimal values for unspecified parameters when forming a new network.
-     *
-     * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
-     * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
-     * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
-     * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
-     * create their own network fragments.
-     *
-     * @see #STATE_ATTACHED
-     * @see #getState()
-     */
-    public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
-
-    /**
-     * Attached state.
-     *
-     * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
-     * participating on a network fragment.
-     *
-     * @see #STATE_ATTACHING
-     * @see #getState()
-     */
-    public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
-
-    /**
-     * Fault state.
-     *
-     * <p>The interface will enter this state when the driver has detected some sort of problem from
-     * which it was not immediately able to recover.
-     *
-     * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
-     * cause the device to return to the {@link #STATE_OFFLINE} state.
-     *
-     * @see #getState
-     * @see #STATE_OFFLINE
-     */
-    public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
-
-    /**
-     * Network type for Thread 1.x networks.
-     *
-     * @see android.net.lowpan.LowpanIdentity#getType
-     * @see #getLowpanIdentity
-     * @hide
-     */
-    public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
-
-    public static final String EMPTY_PARTITION_ID = "";
-
-    /**
-     * Callback base class for LowpanInterface
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onConnectedChanged(boolean value) {}
-
-        public void onEnabledChanged(boolean value) {}
-
-        public void onUpChanged(boolean value) {}
-
-        public void onRoleChanged(@NonNull String value) {}
-
-        public void onStateChanged(@NonNull String state) {}
-
-        public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
-
-        public void onLinkNetworkAdded(IpPrefix prefix) {}
-
-        public void onLinkNetworkRemoved(IpPrefix prefix) {}
-
-        public void onLinkAddressAdded(LinkAddress address) {}
-
-        public void onLinkAddressRemoved(LinkAddress address) {}
-    }
-
-    private final ILowpanInterface mBinder;
-    private final Looper mLooper;
-    private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
-
-    /**
-     * Create a new LowpanInterface instance. Applications will almost always want to use {@link
-     * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
-     *
-     * @param context the application context
-     * @param service the Binder interface
-     * @param looper the Binder interface
-     * @hide
-     */
-    public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
-        /* We aren't currently using the context, but if we need
-         * it later on we can easily add it to the class.
-         */
-
-        mBinder = service;
-        mLooper = looper;
-    }
-
-    /**
-     * Returns the ILowpanInterface object associated with this interface.
-     *
-     * @hide
-     */
-    public ILowpanInterface getService() {
-        return mBinder;
-    }
-
-    // Public Actions
-
-    /**
-     * Form a new network with the given network information optional credential. Unspecified fields
-     * in the network information will be filled in with reasonable values. If the network
-     * credential is unspecified, one will be generated automatically.
-     *
-     * <p>This method will block until either the network was successfully formed or an error
-     * prevents the network form being formed.
-     *
-     * <p>Upon success, the interface will be up and attached to the newly formed network.
-     *
-     * @see #join(LowpanProvision)
-     */
-    public void form(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.form(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Attempts to join a new network with the given network information. This method will block
-     * until either the network was successfully joined or an error prevented the network from being
-     * formed. Upon success, the interface will be up and attached to the newly joined network.
-     *
-     * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
-     * device to be present in order for the operation to complete successfully.
-     */
-    public void join(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.join(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Attaches to the network described by identity and credential. This is similar to {@link
-     * #join}, except that (assuming the identity and credential are valid) it will always succeed
-     * and provision the interface, even if there are no peers nearby.
-     *
-     * <p>This method will block execution until the operation has completed.
-     */
-    public void attach(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.attach(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Bring down the network interface and forget all non-volatile details about the current
-     * network.
-     *
-     * <p>This method will block execution until the operation has completed.
-     */
-    public void leave() throws LowpanException {
-        try {
-            mBinder.leave();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Start a new commissioning session. Will fail if the interface is attached to a network or if
-     * the interface is disabled.
-     */
-    public @NonNull LowpanCommissioningSession startCommissioningSession(
-            @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
-        try {
-            mBinder.startCommissioningSession(beaconInfo);
-
-            return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Reset this network interface as if it has been power cycled. Will bring the network interface
-     * down if it was previously up. Will not erase any non-volatile settings.
-     *
-     * <p>This method will block execution until the operation has completed.
-     *
-     * @hide
-     */
-    public void reset() throws LowpanException {
-        try {
-            mBinder.reset();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    // Public Getters and Setters
-
-    /** Returns the name of this network interface. */
-    @NonNull
-    public String getName() {
-        try {
-            return mBinder.getName();
-
-        } catch (DeadObjectException x) {
-            return "";
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if the interface is enabled or disabled.
-     *
-     * @see #setEnabled
-     * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
-     */
-    public boolean isEnabled() {
-        try {
-            return mBinder.isEnabled();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
-     * low-power state and all commands that require the NCP to be queried will fail with {@link
-     * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
-     *
-     * @see #isEnabled
-     * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
-     * @hide
-     */
-    public void setEnabled(boolean enabled) throws LowpanException {
-        try {
-            mBinder.setEnabled(enabled);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Indicates if the network interface is up or down.
-     *
-     * @hide
-     */
-    public boolean isUp() {
-        try {
-            return mBinder.isUp();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if there is at least one peer in range.
-     *
-     * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
-     *     otherwise.
-     */
-    public boolean isConnected() {
-        try {
-            return mBinder.isConnected();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if this interface is currently commissioned onto an existing network. If the
-     * interface is commissioned, the interface may be brought up using setUp().
-     */
-    public boolean isCommissioned() {
-        try {
-            return mBinder.isCommissioned();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Get interface state
-     *
-     * <h3>State Diagram</h3>
-     *
-     * <img src="LowpanInterface-1.png" />
-     *
-     * @return The current state of the interface.
-     * @see #STATE_OFFLINE
-     * @see #STATE_COMMISSIONING
-     * @see #STATE_ATTACHING
-     * @see #STATE_ATTACHED
-     * @see #STATE_FAULT
-     */
-    public String getState() {
-        try {
-            return mBinder.getState();
-
-        } catch (DeadObjectException x) {
-            return STATE_FAULT;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** Get network partition/fragment identifier. */
-    public String getPartitionId() {
-        try {
-            return mBinder.getPartitionId();
-
-        } catch (DeadObjectException x) {
-            return EMPTY_PARTITION_ID;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    public LowpanIdentity getLowpanIdentity() {
-        try {
-            return mBinder.getLowpanIdentity();
-
-        } catch (DeadObjectException x) {
-            return new LowpanIdentity();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    @NonNull
-    public String getRole() {
-        try {
-            return mBinder.getRole();
-
-        } catch (DeadObjectException x) {
-            return ROLE_DETACHED;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    @Nullable
-    public LowpanCredential getLowpanCredential() {
-        try {
-            return mBinder.getLowpanCredential();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
-        try {
-            return mBinder.getSupportedNetworkTypes();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
-        try {
-            return mBinder.getSupportedChannels();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    // Listener Support
-
-    /**
-     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
-     * @param handler If not <code>null</code>, events will be dispatched via the given handler
-     *     object. If <code>null</code>, the thread upon which events will be dispatched is
-     *     unspecified.
-     * @see #registerCallback(Callback)
-     * @see #unregisterCallback(Callback)
-     */
-    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
-        ILowpanInterfaceListener.Stub listenerBinder =
-                new ILowpanInterfaceListener.Stub() {
-                    private Handler mHandler;
-
-                    {
-                        if (handler != null) {
-                            mHandler = handler;
-                        } else if (mLooper != null) {
-                            mHandler = new Handler(mLooper);
-                        } else {
-                            mHandler = new Handler();
-                        }
-                    }
-
-                    @Override
-                    public void onEnabledChanged(boolean value) {
-                        mHandler.post(() -> cb.onEnabledChanged(value));
-                    }
-
-                    @Override
-                    public void onConnectedChanged(boolean value) {
-                        mHandler.post(() -> cb.onConnectedChanged(value));
-                    }
-
-                    @Override
-                    public void onUpChanged(boolean value) {
-                        mHandler.post(() -> cb.onUpChanged(value));
-                    }
-
-                    @Override
-                    public void onRoleChanged(String value) {
-                        mHandler.post(() -> cb.onRoleChanged(value));
-                    }
-
-                    @Override
-                    public void onStateChanged(String value) {
-                        mHandler.post(() -> cb.onStateChanged(value));
-                    }
-
-                    @Override
-                    public void onLowpanIdentityChanged(LowpanIdentity value) {
-                        mHandler.post(() -> cb.onLowpanIdentityChanged(value));
-                    }
-
-                    @Override
-                    public void onLinkNetworkAdded(IpPrefix value) {
-                        mHandler.post(() -> cb.onLinkNetworkAdded(value));
-                    }
-
-                    @Override
-                    public void onLinkNetworkRemoved(IpPrefix value) {
-                        mHandler.post(() -> cb.onLinkNetworkRemoved(value));
-                    }
-
-                    @Override
-                    public void onLinkAddressAdded(String value) {
-                        LinkAddress la;
-                        try {
-                            la = new LinkAddress(value);
-                        } catch (IllegalArgumentException x) {
-                            Log.e(
-                                    TAG,
-                                    "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
-                            return;
-                        }
-                        mHandler.post(() -> cb.onLinkAddressAdded(la));
-                    }
-
-                    @Override
-                    public void onLinkAddressRemoved(String value) {
-                        LinkAddress la;
-                        try {
-                            la = new LinkAddress(value);
-                        } catch (IllegalArgumentException x) {
-                            Log.e(
-                                    TAG,
-                                    "onLinkAddressRemoved: Bad LinkAddress \""
-                                            + value
-                                            + "\", "
-                                            + x);
-                            return;
-                        }
-                        mHandler.post(() -> cb.onLinkAddressRemoved(la));
-                    }
-
-                    @Override
-                    public void onReceiveFromCommissioner(byte[] packet) {
-                        // This is only used by the LowpanCommissioningSession.
-                    }
-                };
-        try {
-            mBinder.addListener(listenerBinder);
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-
-        synchronized (mListenerMap) {
-            mListenerMap.put(System.identityHashCode(cb), listenerBinder);
-        }
-    }
-
-    /**
-     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
-     *
-     * <p>The thread upon which events will be dispatched is unspecified.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
-     * @see #registerCallback(Callback, Handler)
-     * @see #unregisterCallback(Callback)
-     */
-    public void registerCallback(Callback cb) {
-        registerCallback(cb, null);
-    }
-
-    /**
-     * Unregisters a previously registered callback class.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
-     *     receive events.
-     * @see #registerCallback(Callback, Handler)
-     * @see #registerCallback(Callback)
-     */
-    public void unregisterCallback(Callback cb) {
-        int hashCode = System.identityHashCode(cb);
-        synchronized (mListenerMap) {
-            ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
-
-            if (listenerBinder != null) {
-                mListenerMap.remove(hashCode);
-
-                try {
-                    mBinder.removeListener(listenerBinder);
-                } catch (DeadObjectException x) {
-                    // We ignore a dead object exception because that
-                    // pretty clearly means our callback isn't registered.
-                } catch (RemoteException x) {
-                    throw x.rethrowAsRuntimeException();
-                }
-            }
-        }
-    }
-
-    // Active and Passive Scanning
-
-    /**
-     * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
-     *
-     * <p>This method allocates a new unique object for each call.
-     *
-     * @see android.net.lowpan.LowpanScanner
-     */
-    public @NonNull LowpanScanner createScanner() {
-        return new LowpanScanner(mBinder);
-    }
-
-    // Route Management
-
-    /**
-     * Makes a copy of the internal list of LinkAddresses.
-     *
-     * @hide
-     */
-    public LinkAddress[] getLinkAddresses() throws LowpanException {
-        try {
-            String[] linkAddressStrings = mBinder.getLinkAddresses();
-            LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
-            int i = 0;
-            for (String str : linkAddressStrings) {
-                ret[i++] = new LinkAddress(str);
-            }
-            return ret;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Makes a copy of the internal list of networks reachable on via this link.
-     *
-     * @hide
-     */
-    public IpPrefix[] getLinkNetworks() throws LowpanException {
-        try {
-            return mBinder.getLinkNetworks();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Advertise the given IP prefix as an on-mesh prefix.
-     *
-     * @hide
-     */
-    public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
-        try {
-            mBinder.addOnMeshPrefix(prefix, flags);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
-     * prefixes.
-     *
-     * @hide
-     */
-    public void removeOnMeshPrefix(IpPrefix prefix) {
-        try {
-            mBinder.removeOnMeshPrefix(prefix);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service exceptions
-            Log.e(TAG, x.toString());
-        }
-    }
-
-    /**
-     * Advertise this device to other devices on the mesh network as having a specific route to the
-     * given network. This device will then receive forwarded traffic for that network.
-     *
-     * @hide
-     */
-    public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
-        try {
-            mBinder.addExternalRoute(prefix, flags);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Revoke a previously advertised specific route to the given network.
-     *
-     * @hide
-     */
-    public void removeExternalRoute(IpPrefix prefix) {
-        try {
-            mBinder.removeExternalRoute(prefix);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service exceptions
-            Log.e(TAG, x.toString());
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
deleted file mode 100644
index 33b35e6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * Manager object for looking up LoWPAN interfaces.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanManager {
-    private static final String TAG = LowpanManager.class.getSimpleName();
-
-    /** @hide */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
-
-        public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
-    }
-
-    private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
-    private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
-
-    /* This is a WeakHashMap because we don't want to hold onto
-     * a strong reference to ILowpanInterface, so that it can be
-     * garbage collected if it isn't being used anymore. Since
-     * the value class holds onto this specific ILowpanInterface,
-     * we also need to have a weak reference to the value.
-     * This design pattern allows us to skip removal of items
-     * from this Map without leaking memory.
-     */
-    private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
-            new WeakHashMap<>();
-
-    private final ILowpanManager mService;
-    private final Context mContext;
-    private final Looper mLooper;
-
-    // Static Methods
-
-    public static LowpanManager from(Context context) {
-        return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
-    }
-
-    /** @hide */
-    public static LowpanManager getManager() {
-        IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
-
-        if (binder != null) {
-            ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
-            return new LowpanManager(service);
-        }
-
-        return null;
-    }
-
-    // Constructors
-
-    LowpanManager(ILowpanManager service) {
-        mService = service;
-        mContext = null;
-        mLooper = null;
-    }
-
-    /**
-     * Create a new LowpanManager instance. Applications will almost always want to use {@link
-     * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
-     * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
-     *
-     * @param context the application context
-     * @param service the Binder interface
-     * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
-     *     private class.
-     */
-    public LowpanManager(Context context, ILowpanManager service) {
-        this(context, service, BackgroundThread.get().getLooper());
-    }
-
-    @VisibleForTesting
-    public LowpanManager(Context context, ILowpanManager service, Looper looper) {
-        mContext = context;
-        mService = service;
-        mLooper = looper;
-    }
-
-    /** @hide */
-    @Nullable
-    public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
-        LowpanInterface iface = null;
-
-        synchronized (mBinderCache) {
-            if (mBinderCache.containsKey(ifaceService.asBinder())) {
-                iface = mBinderCache.get(ifaceService.asBinder()).get();
-            }
-        }
-
-        return iface;
-    }
-
-    /** @hide */
-    @Nullable
-    public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
-        LowpanInterface iface = null;
-
-        try {
-            synchronized (mBinderCache) {
-                if (mBinderCache.containsKey(ifaceService.asBinder())) {
-                    iface = mBinderCache.get(ifaceService.asBinder()).get();
-                }
-
-                if (iface == null) {
-                    String ifaceName = ifaceService.getName();
-
-                    iface = new LowpanInterface(mContext, ifaceService, mLooper);
-
-                    synchronized (mInterfaceCache) {
-                        mInterfaceCache.put(iface.getName(), iface);
-                    }
-
-                    mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
-
-                    /* Make sure we remove the object from the
-                     * interface cache if the associated service
-                     * dies.
-                     */
-                    ifaceService
-                            .asBinder()
-                            .linkToDeath(
-                                    new IBinder.DeathRecipient() {
-                                        @Override
-                                        public void binderDied() {
-                                            synchronized (mInterfaceCache) {
-                                                LowpanInterface iface =
-                                                        mInterfaceCache.get(ifaceName);
-
-                                                if ((iface != null)
-                                                        && (iface.getService() == ifaceService)) {
-                                                    mInterfaceCache.remove(ifaceName);
-                                                }
-                                            }
-                                        }
-                                    },
-                                    0);
-                }
-            }
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-
-        return iface;
-    }
-
-    /**
-     * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
-     * exist, or it is not a LoWPAN interface, returns null.
-     */
-    @Nullable
-    public LowpanInterface getInterface(@NonNull String name) {
-        LowpanInterface iface = null;
-
-        try {
-            /* This synchronized block covers both branches of the enclosed
-             * if() statement in order to avoid a race condition. Two threads
-             * calling getInterface() with the same name would race to create
-             * the associated LowpanInterface object, creating two of them.
-             * Having the whole block be synchronized avoids that race.
-             */
-            synchronized (mInterfaceCache) {
-                if (mInterfaceCache.containsKey(name)) {
-                    iface = mInterfaceCache.get(name);
-
-                } else {
-                    ILowpanInterface ifaceService = mService.getInterface(name);
-
-                    if (ifaceService != null) {
-                        iface = getInterface(ifaceService);
-                    }
-                }
-            }
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-
-        return iface;
-    }
-
-    /**
-     * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
-     * interfaces registered, returns null.
-     */
-    @Nullable
-    public LowpanInterface getInterface() {
-        String[] ifaceList = getInterfaceList();
-        if (ifaceList.length > 0) {
-            return getInterface(ifaceList[0]);
-        }
-        return null;
-    }
-
-    /**
-     * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
-     * interfaces if the calling process does not have permissions to see individual interfaces.
-     */
-    @NonNull
-    public String[] getInterfaceList() {
-        try {
-            return mService.getInterfaceList();
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Registers a callback object to receive notifications when LoWPAN interfaces are added or
-     * removed.
-     *
-     * @hide
-     */
-    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
-            throws LowpanException {
-        ILowpanManagerListener.Stub listenerBinder =
-                new ILowpanManagerListener.Stub() {
-                    private Handler mHandler;
-
-                    {
-                        if (handler != null) {
-                            mHandler = handler;
-                        } else if (mLooper != null) {
-                            mHandler = new Handler(mLooper);
-                        } else {
-                            mHandler = new Handler();
-                        }
-                    }
-
-                    @Override
-                    public void onInterfaceAdded(ILowpanInterface ifaceService) {
-                        Runnable runnable =
-                                () -> {
-                                    LowpanInterface iface = getInterface(ifaceService);
-
-                                    if (iface != null) {
-                                        cb.onInterfaceAdded(iface);
-                                    }
-                                };
-
-                        mHandler.post(runnable);
-                    }
-
-                    @Override
-                    public void onInterfaceRemoved(ILowpanInterface ifaceService) {
-                        Runnable runnable =
-                                () -> {
-                                    LowpanInterface iface = getInterfaceNoCreate(ifaceService);
-
-                                    if (iface != null) {
-                                        cb.onInterfaceRemoved(iface);
-                                    }
-                                };
-
-                        mHandler.post(runnable);
-                    }
-                };
-        try {
-            mService.addListener(listenerBinder);
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-
-        synchronized (mListenerMap) {
-            mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
-        }
-    }
-
-    /** @hide */
-    public void registerCallback(@NonNull Callback cb) throws LowpanException {
-        registerCallback(cb, null);
-    }
-
-    /**
-     * Unregisters a previously registered {@link LowpanManager.Callback} object.
-     *
-     * @hide
-     */
-    public void unregisterCallback(@NonNull Callback cb) {
-        Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
-        ILowpanManagerListener listenerBinder = null;
-
-        synchronized (mListenerMap) {
-            listenerBinder = mListenerMap.get(hashCode);
-            mListenerMap.remove(hashCode);
-        }
-
-        if (listenerBinder != null) {
-            try {
-                mService.removeListener(listenerBinder);
-            } catch (RemoteException x) {
-                throw x.rethrowFromSystemServer();
-            }
-        } else {
-            throw new RuntimeException("Attempt to unregister an unknown callback");
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
deleted file mode 100644
index cc45ff85..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-public final class LowpanProperties {
-
-    public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
-            new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
-
-    public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
-            new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
-
-    /** @hide */
-    private LowpanProperties() {}
-
-    /** @hide */
-    static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
-        private final String mName;
-        private final Class<T> mType;
-
-        LowpanStandardProperty(String name, Class<T> type) {
-            mName = name;
-            mType = type;
-        }
-
-        @Override
-        public String getName() {
-            return mName;
-        }
-
-        @Override
-        public Class<T> getType() {
-            return mType;
-        }
-
-        @Override
-        public String toString() {
-            return getName();
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
deleted file mode 100644
index 7f26986..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperty.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import java.util.Map;
-
-/** {@hide} */
-public abstract class LowpanProperty<T> {
-    public abstract String getName();
-
-    public abstract Class<T> getType();
-
-    public void putInMap(Map map, T value) {
-        map.put(getName(), value);
-    }
-
-    public T getFromMap(Map map) {
-        return (T) map.get(getName());
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.aidl b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
deleted file mode 100644
index 100e9dc..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanProvision cpp_header "android/net/lowpan/LowpanProvision.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
deleted file mode 100644
index 68c8709..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Describes the information needed to describe a network
- *
- * @hide
- */
-// @SystemApi
-public class LowpanProvision implements Parcelable {
-
-    // Builder
-
-    /** @hide */
-    // @SystemApi
-    public static class Builder {
-        private final LowpanProvision provision = new LowpanProvision();
-
-        public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
-            provision.mIdentity = identity;
-            return this;
-        }
-
-        public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
-            provision.mCredential = credential;
-            return this;
-        }
-
-        public LowpanProvision build() {
-            return provision;
-        }
-    }
-
-    private LowpanProvision() {}
-
-    // Instance Variables
-
-    private LowpanIdentity mIdentity = new LowpanIdentity();
-    private LowpanCredential mCredential = null;
-
-    // Public Getters and Setters
-
-    @NonNull
-    public LowpanIdentity getLowpanIdentity() {
-        return mIdentity;
-    }
-
-    @Nullable
-    public LowpanCredential getLowpanCredential() {
-        return mCredential;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
-
-        if (mCredential != null) {
-            sb.append(", credential => ").append(mCredential.toString());
-        }
-
-        sb.append("}");
-
-        return sb.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIdentity, mCredential);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanProvision)) {
-            return false;
-        }
-        LowpanProvision rhs = (LowpanProvision) obj;
-
-        if (!mIdentity.equals(rhs.mIdentity)) {
-            return false;
-        }
-
-        if (!Objects.equals(mCredential, rhs.mCredential)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mIdentity.writeToParcel(dest, flags);
-        if (mCredential == null) {
-            dest.writeBoolean(false);
-        } else {
-            dest.writeBoolean(true);
-            mCredential.writeToParcel(dest, flags);
-        }
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanProvision> CREATOR =
-            new Creator<LowpanProvision>() {
-                public LowpanProvision createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
-                    if (in.readBoolean()) {
-                        builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
-                    }
-
-                    return builder.build();
-                }
-
-                public LowpanProvision[] newArray(int size) {
-                    return new LowpanProvision[size];
-                }
-            };
-};
diff --git a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
deleted file mode 100644
index 71a5a13..0000000
--- a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.util.AndroidRuntimeException;
-
-/**
- * Generic runtime exception for LoWPAN operations.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanRuntimeException extends AndroidRuntimeException {
-
-    public LowpanRuntimeException() {}
-
-    public LowpanRuntimeException(String message) {
-        super(message);
-    }
-
-    public LowpanRuntimeException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public LowpanRuntimeException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
deleted file mode 100644
index 59156c4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * LoWPAN Scanner
- *
- * <p>This class allows performing network (active) scans and energy (passive) scans.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanScanner {
-    private static final String TAG = LowpanScanner.class.getSimpleName();
-
-    // Public Classes
-
-    /**
-     * Callback base class for LowpanScanner
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
-
-        public void onEnergyScanResult(LowpanEnergyScanResult result) {}
-
-        public void onScanFinished() {}
-    }
-
-    // Instance Variables
-
-    private ILowpanInterface mBinder;
-    private Callback mCallback = null;
-    private Handler mHandler = null;
-    private ArrayList<Integer> mChannelMask = null;
-    private int mTxPower = Integer.MAX_VALUE;
-
-    // Constructors/Accessors and Exception Glue
-
-    LowpanScanner(@NonNull ILowpanInterface binder) {
-        mBinder = binder;
-    }
-
-    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
-    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
-        mCallback = cb;
-        mHandler = handler;
-    }
-
-    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
-    public void setCallback(@Nullable Callback cb) {
-        setCallback(cb, null);
-    }
-
-    /**
-     * Sets the channel mask to use when scanning.
-     *
-     * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
-     *     channel mask will be cleared and all channels not masked by the current regulatory zone
-     *     will be scanned.
-     */
-    public void setChannelMask(@Nullable Collection<Integer> mask) {
-        if (mask == null) {
-            mChannelMask = null;
-        } else {
-            if (mChannelMask == null) {
-                mChannelMask = new ArrayList<>();
-            } else {
-                mChannelMask.clear();
-            }
-            mChannelMask.addAll(mask);
-        }
-    }
-
-    /**
-     * Gets the current channel mask.
-     *
-     * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
-     */
-    public @Nullable Collection<Integer> getChannelMask() {
-        return (Collection<Integer>) mChannelMask.clone();
-    }
-
-    /**
-     * Adds a channel to the channel mask used for scanning.
-     *
-     * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
-     * this channel. May be called multiple times to add additional channels ot the channel mask.
-     *
-     * @see #setChannelMask
-     * @see #getChannelMask
-     * @see #getTxPower
-     */
-    public void addChannel(int channel) {
-        if (mChannelMask == null) {
-            mChannelMask = new ArrayList<>();
-        }
-        mChannelMask.add(Integer.valueOf(channel));
-    }
-
-    /**
-     * Sets the maximum transmit power to be used for active scanning.
-     *
-     * <p>The actual transmit power used is the lesser of this value and the currently configured
-     * maximum transmit power for the interface.
-     *
-     * @see #getTxPower
-     */
-    public void setTxPower(int txPower) {
-        mTxPower = txPower;
-    }
-
-    /**
-     * Gets the maximum transmit power used for active scanning.
-     *
-     * @see #setTxPower
-     */
-    public int getTxPower() {
-        return mTxPower;
-    }
-
-    private Map<String, Object> createScanOptionMap() {
-        Map<String, Object> map = new HashMap();
-
-        if (mChannelMask != null) {
-            LowpanProperties.KEY_CHANNEL_MASK.putInMap(
-                    map, mChannelMask.stream().mapToInt(i -> i).toArray());
-        }
-
-        if (mTxPower != Integer.MAX_VALUE) {
-            LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
-        }
-
-        return map;
-    }
-
-    /**
-     * Start a network scan.
-     *
-     * <p>This method will return once the scan has started.
-     *
-     * @see #stopNetScan
-     */
-    public void startNetScan() throws LowpanException {
-        Map<String, Object> map = createScanOptionMap();
-
-        ILowpanNetScanCallback binderListener =
-                new ILowpanNetScanCallback.Stub() {
-                    public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
-                        Callback callback;
-                        Handler handler;
-
-                        synchronized (LowpanScanner.this) {
-                            callback = mCallback;
-                            handler = mHandler;
-                        }
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-
-                    public void onNetScanFinished() {
-                        Callback callback;
-                        Handler handler;
-
-                        synchronized (LowpanScanner.this) {
-                            callback = mCallback;
-                            handler = mHandler;
-                        }
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onScanFinished();
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-                };
-
-        try {
-            mBinder.startNetScan(map, binderListener);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Stop a network scan currently in progress.
-     *
-     * @see #startNetScan
-     */
-    public void stopNetScan() {
-        try {
-            mBinder.stopNetScan();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Start an energy scan.
-     *
-     * <p>This method will return once the scan has started.
-     *
-     * @see #stopEnergyScan
-     */
-    public void startEnergyScan() throws LowpanException {
-        Map<String, Object> map = createScanOptionMap();
-
-        ILowpanEnergyScanCallback binderListener =
-                new ILowpanEnergyScanCallback.Stub() {
-                    public void onEnergyScanResult(int channel, int rssi) {
-                        Callback callback = mCallback;
-                        Handler handler = mHandler;
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable =
-                                () -> {
-                                    if (callback != null) {
-                                        LowpanEnergyScanResult result =
-                                                new LowpanEnergyScanResult();
-                                        result.setChannel(channel);
-                                        result.setMaxRssi(rssi);
-                                        callback.onEnergyScanResult(result);
-                                    }
-                                };
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-
-                    public void onEnergyScanFinished() {
-                        Callback callback = mCallback;
-                        Handler handler = mHandler;
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onScanFinished();
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-                };
-
-        try {
-            mBinder.startEnergyScan(map, binderListener);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Stop an energy scan currently in progress.
-     *
-     * @see #startEnergyScan
-     */
-    public void stopEnergyScan() {
-        try {
-            mBinder.stopEnergyScan();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
deleted file mode 100644
index 90ef498..0000000
--- a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the form operation found a network nearby with the same identity.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class NetworkAlreadyExistsException extends LowpanException {
-
-    public NetworkAlreadyExistsException() {}
-
-    public NetworkAlreadyExistsException(String message) {
-        super(message, null);
-    }
-
-    public NetworkAlreadyExistsException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public NetworkAlreadyExistsException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/OperationCanceledException.java b/lowpan/java/android/net/lowpan/OperationCanceledException.java
deleted file mode 100644
index fcafe3a..0000000
--- a/lowpan/java/android/net/lowpan/OperationCanceledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation was canceled by the driver before it could finish.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class OperationCanceledException extends LowpanException {
-
-    public OperationCanceledException() {}
-
-    public OperationCanceledException(String message) {
-        super(message);
-    }
-
-    public OperationCanceledException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected OperationCanceledException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/WrongStateException.java b/lowpan/java/android/net/lowpan/WrongStateException.java
deleted file mode 100644
index 3565419..0000000
--- a/lowpan/java/android/net/lowpan/WrongStateException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the interface is the wrong state for an operation.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class WrongStateException extends LowpanException {
-
-    public WrongStateException() {}
-
-    public WrongStateException(String message) {
-        super(message);
-    }
-
-    public WrongStateException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected WrongStateException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
deleted file mode 100644
index 342e32e..0000000
--- a/lowpan/java/android/net/lowpan/package.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<HTML>
-<BODY>
-<p>@SystemApi</p>
-<!-- @hide -->
-<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
-Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
-<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
-<p>The LoWPAN APIs provide a means by which applications can communicate
-with the lower-level wireless stack that provides LoWPAN network access.</p>
-
-<p>Some APIs may require the following user permissions:</p>
-<ul>
-  <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
-  <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
-  <li>TBD</li>
-</ul>
-
-<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
-If your application uses these APIs, declare so with a <a
-href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
-element in the manifest file:</p>
-<pre>
-&lt;manifest ...>
-    &lt;uses-feature android:name="android.hardware.lowpan" />
-    ...
-&lt;/manifest>
-</pre>
-</BODY>
-</HTML>
diff --git a/lowpan/tests/Android.bp b/lowpan/tests/Android.bp
deleted file mode 100644
index 5908929..0000000
--- a/lowpan/tests/Android.bp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Make test APK
-// ============================================================
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "FrameworksLowpanApiTests",
-    srcs: ["**/*.java"],
-    // Filter all src files to just java files
-    jacoco: {
-        include_filter: ["android.net.lowpan.*"],
-        exclude_filter: [
-	    "android.net.lowpan.LowpanInterfaceTest*",
-	    "android.net.lowpan.LowpanManagerTest*",
-	],
-    },
-    static_libs: [
-        "androidx.test.rules",
-        "guava",
-        "mockito-target-minus-junit4",
-        "frameworks-base-testutils",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    platform_apis: true,
-    test_suites: ["device-tests"],
-    certificate: "platform",
-}
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
deleted file mode 100644
index 8e68fc7..0000000
--- a/lowpan/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.lowpan.test">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:label="LowpanTestDummyLabel"
-                  android:name="LowpanTestDummyName"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.net.lowpan.test"
-        android:label="Frameworks LoWPAN API Tests">
-    </instrumentation>
-
-</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
deleted file mode 100644
index 978cc02..0000000
--- a/lowpan/tests/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Runs Frameworks LoWPAN API Tests.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="FrameworksLowpanApiTests" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.lowpan.test" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-    </test>
-</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
deleted file mode 100644
index cb5772e..0000000
--- a/lowpan/tests/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# LoWPAN Unit Tests
-This package contains unit tests for the android LoWPAN framework System APIs based on the
-[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
-The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
-libraries.
-
-## Running Tests
-The easiest way to run tests is simply run
-
-```
-frameworks/base/lowpan/tests/runtests.sh
-```
-
-`runtests.sh` will build the test project and all of its dependencies and push the APK to the
-connected device. It will then run the tests on the device.
-
-To pick up changes in framework/base, you will need to:
-1. rebuild the framework library 'make -j32'
-2. sync over the updated library to the device 'adb sync'
-3. restart framework on the device 'adb shell stop' then 'adb shell start'
-
-To enable syncing data to the device for first time after clean reflash:
-1. adb disable-verity
-2. adb reboot
-3. adb remount
-
-See below for a few example of options to limit which tests are run.
-See the
-[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
-for more details on the supported options.
-
-```
-runtests.sh -e package android.net.lowpan
-runtests.sh -e class android.net.lowpan.LowpanManagerTest
-```
-
-If you manually build and push the test APK to the device you can run tests using
-
-```
-adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
-```
-
-## Adding Tests
-Tests can be added by adding classes to the src directory. JUnit4 style test cases can
-be written by simply annotating test methods with `org.junit.Test`.
-
-## Debugging Tests
-If you are trying to debug why tests are not doing what you expected, you can add android log
-statements and use logcat to view them. The beginning and end of every tests is automatically logged
-with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
deleted file mode 100755
index 8267a79..0000000
--- a/lowpan/tests/runtests.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-
-if [ -z $ANDROID_BUILD_TOP ]; then
-  echo "You need to source and lunch before you can use this script"
-  exit 1
-fi
-
-echo "Running tests"
-
-set -e # fail early
-
-echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
-# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
-#      caller.
-make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
-
-set -x # print commands
-
-adb root
-adb wait-for-device
-
-adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-
-adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
deleted file mode 100644
index 86f9d0e..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Map;
-
-/** Unit tests for android.net.lowpan.LowpanInterface. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanInterfaceTest {
-    private static final String TEST_PACKAGE_NAME = "TestPackage";
-
-    @Mock Context mContext;
-    @Mock ILowpanInterface mLowpanInterfaceService;
-    @Mock IBinder mLowpanInterfaceBinder;
-    @Mock ApplicationInfo mApplicationInfo;
-    @Mock IBinder mAppBinder;
-    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
-
-    private Handler mHandler;
-    private final TestLooper mTestLooper = new TestLooper();
-    private ILowpanInterfaceListener mInterfaceListener;
-    private LowpanInterface mLowpanInterface;
-    private Map<String, Object> mPropertyMap;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        mLowpanInterface =
-                new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
-    }
-
-    @Test
-    public void testStateChangedCallback() throws Exception {
-        // Register our callback
-        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
-
-        // Verify a listener was added
-        verify(mLowpanInterfaceService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mInterfaceListener = listener;
-                                    return listener instanceof ILowpanInterfaceListener;
-                                }));
-
-        // Change some properties
-        mInterfaceListener.onStateChanged(LowpanInterface.STATE_OFFLINE);
-        mTestLooper.dispatchAll();
-
-        // Verify that the property was changed
-        verify(mLowpanInterfaceCallback)
-                .onStateChanged(
-                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
-    }
-}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
deleted file mode 100644
index 998e8a5..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Unit tests for android.net.lowpan.LowpanManager. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanManagerTest {
-    private static final String TEST_PACKAGE_NAME = "TestPackage";
-
-    @Mock Context mContext;
-    @Mock ILowpanManager mLowpanService;
-    @Mock ILowpanInterface mLowpanInterfaceService;
-    @Mock IBinder mLowpanInterfaceBinder;
-    @Mock ApplicationInfo mApplicationInfo;
-    @Mock IBinder mAppBinder;
-    @Mock LowpanManager.Callback mLowpanManagerCallback;
-
-    private Handler mHandler;
-    private final TestLooper mTestLooper = new TestLooper();
-    private LowpanManager mLowpanManager;
-
-    private ILowpanManagerListener mManagerListener;
-    private LowpanInterface mLowpanInterface;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
-        mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
-    }
-
-    @Test
-    public void testGetEmptyInterfaceList() throws Exception {
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
-        assertTrue(mLowpanManager.getInterfaceList().length == 0);
-        assertTrue(mLowpanManager.getInterface() == null);
-    }
-
-    @Test
-    public void testGetInterfaceList() throws Exception {
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
-        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-        assertEquals(mLowpanManager.getInterfaceList().length, 1);
-
-        LowpanInterface iface = mLowpanManager.getInterface();
-        assertNotNull(iface);
-        assertEquals(iface.getName(), "wpan0");
-    }
-
-    @Test
-    public void testRegisterCallback() throws Exception {
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        // Register our callback
-        mLowpanManager.registerCallback(mLowpanManagerCallback);
-
-        // Verify a listener was added
-        verify(mLowpanService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mManagerListener = listener;
-                                    return listener instanceof ILowpanManagerListener;
-                                }));
-
-        // Add an interface
-        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was added
-        verify(mLowpanManagerCallback)
-                .onInterfaceAdded(
-                        argThat(
-                                iface -> {
-                                    mLowpanInterface = iface;
-                                    return iface instanceof LowpanInterface;
-                                }));
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-
-        // This check causes the test to fail with a weird error, but I'm not sure why.
-        assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
-
-        // Verify that calling getInterface on the LowpanManager object will yield the same
-        // LowpanInterface object.
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
-        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
-        assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
-
-        // Remove the service
-        mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was removed
-        verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
-    }
-
-    @Test
-    public void testUnregisterCallback() throws Exception {
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        // Register our callback
-        mLowpanManager.registerCallback(mLowpanManagerCallback);
-
-        // Verify a listener was added
-        verify(mLowpanService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mManagerListener = listener;
-                                    return listener instanceof ILowpanManagerListener;
-                                }));
-
-        // Add an interface
-        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was added
-        verify(mLowpanManagerCallback)
-                .onInterfaceAdded(
-                        argThat(
-                                iface -> {
-                                    mLowpanInterface = iface;
-                                    return iface instanceof LowpanInterface;
-                                }));
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-
-        // Unregister our callback
-        mLowpanManager.unregisterCallback(mLowpanManagerCallback);
-
-        // Verify the listener was removed
-        verify(mLowpanService).removeListener(mManagerListener);
-
-        // Verify that the callback wasn't invoked.
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-    }
-}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index fe15f0e..29bfd1a 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -26,9 +26,7 @@
 oneway interface IMediaRouter2 {
     void notifyRouterRegistered(in List<MediaRoute2Info> currentRoutes,
             in RoutingSessionInfo currentSystemSessionInfo);
-    void notifyRoutesAdded(in List<MediaRoute2Info> routes);
-    void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
-    void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+    void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
     void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 71dc2a7..9f3c3ff 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,8 +30,6 @@
     void notifySessionReleased(in RoutingSessionInfo session);
     void notifyDiscoveryPreferenceChanged(String packageName,
             in RouteDiscoveryPreference discoveryPreference);
-    void notifyRoutesAdded(in List<MediaRoute2Info> routes);
-    void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
-    void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+    void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifyRequestFailed(int requestId, int reason);
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a7a21e7..26cb9f8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -132,7 +132,7 @@
     /**
      * Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
      * dispatch. This is only used to determine what callback a route should be assigned to (added,
-     * removed, changed) in {@link #dispatchFilteredRoutesChangedLocked(List)}.
+     * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
      */
     private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
 
@@ -820,7 +820,7 @@
         }
     }
 
-    void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) {
+    void dispatchFilteredRoutesUpdatedOnHandler(List<MediaRoute2Info> newRoutes) {
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
         List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -863,29 +863,16 @@
         if (!changedRoutes.isEmpty()) {
             notifyRoutesChanged(changedRoutes);
         }
-    }
 
-    void addRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getId(), route);
-            }
-            updateFilteredRoutesLocked();
+        // Note: We don't notify clients of changes in route ordering.
+        if (!addedRoutes.isEmpty() || !removedRoutes.isEmpty() || !changedRoutes.isEmpty()) {
+            notifyRoutesUpdated(newRoutes);
         }
     }
 
-    void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
+    void updateRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getId());
-            }
-            updateFilteredRoutesLocked();
-        }
-    }
-
-    void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-        synchronized (mLock) {
+            mRoutes.clear();
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
             }
@@ -900,8 +887,10 @@
                 Collections.unmodifiableList(
                         filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values())));
         mHandler.sendMessage(
-                obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked,
-                        this, mFilteredRoutes));
+                obtainMessage(
+                        MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
+                        this,
+                        mFilteredRoutes));
     }
 
     /**
@@ -1211,6 +1200,14 @@
         }
     }
 
+    private void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+        for (RouteCallbackRecord record : mRouteCallbackRecords) {
+            List<MediaRoute2Info> filteredRoutes =
+                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+            record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
+        }
+    }
+
     private void notifyPreferredFeaturesChanged(List<String> features) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             record.mExecutor.execute(
@@ -1246,29 +1243,44 @@
     /** Callback for receiving events about media route discovery. */
     public abstract static class RouteCallback {
         /**
-         * Called when routes are added. Whenever you registers a callback, this will be invoked
-         * with known routes.
+         * Called when routes are added. Whenever you register a callback, this will be invoked with
+         * known routes.
          *
          * @param routes the list of routes that have been added. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when routes are removed.
          *
          * @param routes the list of routes that have been removed. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when routes are changed. For example, it is called when the route's name or volume
-         * have been changed.
+         * Called when the properties of one or more existing routes are changed. For example, it is
+         * called when a route's name or volume have changed.
          *
          * @param routes the list of routes that have been changed. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
+         * Called when the route list is updated, which can happen when routes are added, removed,
+         * or modified. It will also be called when a route callback is registered.
+         *
+         * @param routes the updated list of routes filtered by the callback's individual discovery
+         *     preferences.
+         */
+        public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {}
+
+        /**
          * Called when the client app's preferred features are changed. When this is called, it is
          * recommended to {@link #getRoutes()} to get the routes that are currently available to the
          * app.
@@ -1985,21 +1997,9 @@
         }
 
         @Override
-        public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
+        public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::changeRoutesOnHandler, MediaRouter2.this, routes));
+                    obtainMessage(MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes));
         }
 
         @Override
@@ -2047,17 +2047,7 @@
     class ManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
-        public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
-            updateAllRoutesFromManager();
-        }
-
-        @Override
-        public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
-            updateAllRoutesFromManager();
-        }
-
-        @Override
-        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
+        public void onRoutesUpdated() {
             updateAllRoutesFromManager();
         }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 44c0b54..8afc7d9 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -546,37 +546,15 @@
         }
     }
 
-    void addRoutesOnHandler(List<MediaRoute2Info> routes) {
+    void updateRoutesOnHandler(@NonNull List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
+            mRoutes.clear();
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
             }
         }
-        if (routes.size() > 0) {
-            notifyRoutesAdded(routes);
-        }
-    }
 
-    void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getId());
-            }
-        }
-        if (routes.size() > 0) {
-            notifyRoutesRemoved(routes);
-        }
-    }
-
-    void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getId(), route);
-            }
-        }
-        if (routes.size() > 0) {
-            notifyRoutesChanged(routes);
-        }
+        notifyRoutesUpdated();
     }
 
     void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
@@ -650,24 +628,9 @@
         notifySessionUpdated(sessionInfo);
     }
 
-    private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
+    private void notifyRoutesUpdated() {
         for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesAdded(routes));
-        }
-    }
-
-    private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesRemoved(routes));
-        }
-    }
-
-    private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesChanged(routes));
+            record.mExecutor.execute(() -> record.mCallback.onRoutesUpdated());
         }
     }
 
@@ -963,23 +926,12 @@
      * Interface for receiving events about media routing changes.
      */
     public interface Callback {
-        /**
-         * Called when routes are added.
-         * @param routes the list of routes that have been added. It's never empty.
-         */
-        default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when routes are removed.
-         * @param routes the list of routes that have been removed. It's never empty.
+         * Called when the routes list changes. This includes adding, modifying, or removing
+         * individual routes.
          */
-        default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
-
-        /**
-         * Called when routes are changed.
-         * @param routes the list of routes that have been changed. It's never empty.
-         */
-        default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+        default void onRoutesUpdated() {}
 
         /**
          * Called when a session is changed.
@@ -1115,21 +1067,12 @@
         }
 
         @Override
-        public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
+        public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+            mHandler.sendMessage(
+                    obtainMessage(
+                            MediaRouter2Manager::updateRoutesOnHandler,
+                            MediaRouter2Manager.this,
+                            routes));
         }
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4086dec..37c8367 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -32,7 +32,6 @@
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
 
 import static org.junit.Assert.assertEquals;
@@ -56,10 +55,10 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -69,6 +68,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -115,7 +115,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
                 Manifest.permission.MODIFY_AUDIO_ROUTING);
@@ -170,51 +170,95 @@
     }
 
     @Test
-    public void testOnRoutesRemovedAndAdded() throws Exception {
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouteCallbacks.add(routeCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+    public void testOnRoutesUpdated() throws Exception {
+        final String routeId0 = "routeId0";
+        final String routeName0 = "routeName0";
+        final String routeId1 = "routeId1";
+        final String routeName1 = "routeName1";
+        final List<String> features = Collections.singletonList("customFeature");
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
 
-        CountDownLatch removedLatch = new CountDownLatch(1);
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(new MediaRoute2Info.Builder(routeId0, routeName0).addFeatures(features).build());
+        routes.add(new MediaRoute2Info.Builder(routeId1, routeName1).addFeatures(features).build());
+
         CountDownLatch addedLatch = new CountDownLatch(1);
+        CountDownLatch changedLatch = new CountDownLatch(1);
+        CountDownLatch removedLatch = new CountDownLatch(1);
 
-        addManagerCallback(new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-                assertTrue(routes.size() > 0);
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID2)
-                            && route.getName().equals(ROUTE_NAME2)) {
-                        removedLatch.countDown();
+        addManagerCallback(
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        if (addedLatch.getCount() == 1
+                                && checkRoutesMatch(mManager.getAllRoutes(), routes)) {
+                            addedLatch.countDown();
+                        } else if (changedLatch.getCount() == 1
+                                && checkRoutesMatch(
+                                        mManager.getAllRoutes(), routes.subList(1, 2))) {
+                            changedLatch.countDown();
+                        } else if (removedLatch.getCount() == 1
+                                && checkRoutesRemoved(mManager.getAllRoutes(), routes)) {
+                            removedLatch.countDown();
+                        }
                     }
-                }
+                });
+
+        mService.addRoutes(routes);
+        assertTrue(
+                "Added routes not found or onRoutesUpdated() never called.",
+                addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        MediaRoute2Info newRoute2 =
+                new MediaRoute2Info.Builder(routes.get(1))
+                        .setConnectionState(newConnectionState)
+                        .build();
+        routes.set(1, newRoute2);
+        mService.addRoute(routes.get(1));
+        assertTrue(
+                "Modified route not found or onRoutesUpdated() never called.",
+                changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        List<String> routeIds = new ArrayList<>();
+        routeIds.add(routeId0);
+        routeIds.add(routeId1);
+
+        mService.removeRoutes(routeIds);
+        assertTrue(
+                "Removed routes not found or onRoutesUpdated() never called.",
+                removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private static boolean checkRoutesMatch(
+            List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> expectedRoutes) {
+        for (MediaRoute2Info expectedRoute : expectedRoutes) {
+            MediaRoute2Info matchingRoute =
+                    routesReceived.stream()
+                            .filter(r -> r.getOriginalId().equals(expectedRoute.getOriginalId()))
+                            .findFirst()
+                            .orElse(null);
+
+            if (matchingRoute == null) {
+                return false;
             }
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                assertTrue(routes.size() > 0);
-                if (removedLatch.getCount() > 0) {
-                    return;
-                }
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID2)
-                            && route.getName().equals(ROUTE_NAME2)) {
-                        addedLatch.countDown();
-                    }
-                }
+            assertTrue(TextUtils.equals(expectedRoute.getName(), matchingRoute.getName()));
+            assertEquals(expectedRoute.getFeatures(), matchingRoute.getFeatures());
+            assertEquals(expectedRoute.getConnectionState(), matchingRoute.getConnectionState());
+        }
+
+        return true;
+    }
+
+    private static boolean checkRoutesRemoved(
+            List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> routesRemoved) {
+        for (MediaRoute2Info removedRoute : routesRemoved) {
+            if (routesReceived.stream()
+                    .anyMatch(r -> r.getOriginalId().equals(removedRoute.getOriginalId()))) {
+                return false;
             }
-        });
-
-        MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
-        assertNotNull(routeToRemove);
-
-        mService.removeRoute(ROUTE_ID2);
-        assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        mService.addRoute(routeToRemove);
-        assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        }
+        return true;
     }
 
     @Test
@@ -874,28 +918,31 @@
 
         // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback() {};
-        MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (!route.isSystemRoute()
-                            && hasMatchingFeature(route.getFeatures(), preference
-                            .getPreferredFeatures())) {
-                        addedLatch.countDown();
-                        break;
+        MediaRouter2Manager.Callback managerCallback =
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        List<MediaRoute2Info> routes = mManager.getAllRoutes();
+                        for (MediaRoute2Info route : routes) {
+                            if (!route.isSystemRoute()
+                                    && hasMatchingFeature(
+                                            route.getFeatures(),
+                                            preference.getPreferredFeatures())) {
+                                addedLatch.countDown();
+                                break;
+                            }
+                        }
                     }
-                }
-            }
 
-            @Override
-            public void onDiscoveryPreferenceChanged(String packageName,
-                    RouteDiscoveryPreference discoveryPreference) {
-                if (TextUtils.equals(mPackageName, packageName)
-                        && Objects.equals(preference, discoveryPreference)) {
-                    preferenceLatch.countDown();
-                }
-            }
-        };
+                    @Override
+                    public void onDiscoveryPreferenceChanged(
+                            String packageName, RouteDiscoveryPreference discoveryPreference) {
+                        if (TextUtils.equals(mPackageName, packageName)
+                                && Objects.equals(preference, discoveryPreference)) {
+                            preferenceLatch.countDown();
+                        }
+                    }
+                };
         mManager.registerCallback(mExecutor, managerCallback);
         mRouter2.registerRouteCallback(mExecutor, routeCallback, preference);
 
@@ -923,15 +970,17 @@
     void awaitOnRouteChangedManager(Runnable task, String routeId,
             Predicate<MediaRoute2Info> predicate) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> changed) {
-                MediaRoute2Info route = createRouteMap(changed).get(routeId);
-                if (route != null && predicate.test(route)) {
-                    latch.countDown();
-                }
-            }
-        };
+        MediaRouter2Manager.Callback callback =
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        MediaRoute2Info route =
+                                createRouteMap(mManager.getAllRoutes()).get(routeId);
+                        if (route != null && predicate.test(route)) {
+                            latch.countDown();
+                        }
+                    }
+                };
         mManager.registerCallback(mExecutor, callback);
         try {
             task.run();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index a51e371..a7ae5f4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -30,7 +30,9 @@
 import android.os.IBinder;
 import android.text.TextUtils;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -146,19 +148,44 @@
      * they have the same route id.
      */
     public void addRoute(@NonNull MediaRoute2Info route) {
-        Objects.requireNonNull(route, "route must not be null");
-        mRoutes.put(route.getOriginalId(), route);
-        publishRoutes();
+        addRoutes(Collections.singletonList(route));
     }
 
     /**
-     * Removes a route and publishes it.
+     * Adds a list of routes and publishes it. It will replace existing routes with matching ids.
+     *
+     * @param routes list of routes to be added.
      */
+    public void addRoutes(@NonNull List<MediaRoute2Info> routes) {
+        Objects.requireNonNull(routes, "Routes must not be null.");
+        for (MediaRoute2Info route : routes) {
+            Objects.requireNonNull(route, "Route must not be null");
+            mRoutes.put(route.getOriginalId(), route);
+        }
+        publishRoutes();
+    }
+
+    /** Removes a route and publishes it. */
     public void removeRoute(@NonNull String routeId) {
-        Objects.requireNonNull(routeId, "routeId must not be null");
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route != null) {
-            mRoutes.remove(routeId);
+        removeRoutes(Collections.singletonList(routeId));
+    }
+
+    /**
+     * Removes a list of routes and publishes the changes.
+     *
+     * @param routes list of route ids to be removed.
+     */
+    public void removeRoutes(@NonNull List<String> routes) {
+        Objects.requireNonNull(routes, "Routes must not be null");
+        boolean hasRemovedRoutes = false;
+        for (String routeId : routes) {
+            MediaRoute2Info route = mRoutes.get(routeId);
+            if (route != null) {
+                mRoutes.remove(routeId);
+                hasRemovedRoutes = true;
+            }
+        }
+        if (hasRemovedRoutes) {
             publishRoutes();
         }
     }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 98438cd..b6b8837 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -189,7 +189,8 @@
                         out.write(buffer, 0, bytesRead);
                     }
                 }
-            } catch (IOException | SecurityException | IllegalStateException e) {
+            } catch (IOException | SecurityException | IllegalStateException
+                    | IllegalArgumentException e) {
                 Log.w(LOG_TAG, "Error staging apk from content URI", e);
                 return false;
             }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index a83c090..1df9059 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -58,9 +58,6 @@
         "zxing-core-1.7",
     ],
 
-    // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
-    // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common
-
     resource_dirs: ["res"],
 
     srcs: [
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index f4af9c7..01698b7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,6 +19,7 @@
 import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
 
 import android.app.ActionBar;
+import android.content.res.Configuration;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
 import android.view.LayoutInflater;
@@ -97,7 +98,7 @@
                                         .build()));
             }
         }
-        disableCollapsingToolbarLayoutScrollingBehavior();
+        autoSetCollapsingToolbarLayoutScrolling();
         mToolbar = view.findViewById(R.id.action_bar);
         mContentFrameLayout = view.findViewById(R.id.content_frame);
         final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
@@ -148,7 +149,7 @@
         return mAppBarLayout;
     }
 
-    private void disableCollapsingToolbarLayoutScrollingBehavior() {
+    private void autoSetCollapsingToolbarLayoutScrolling() {
         if (mAppBarLayout == null) {
             return;
         }
@@ -159,7 +160,9 @@
                 new AppBarLayout.Behavior.DragCallback() {
                     @Override
                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
-                        return false;
+                        // Header can be scrolling while device in landscape mode.
+                        return appBarLayout.getResources().getConfiguration().orientation
+                                == Configuration.ORIENTATION_LANDSCAPE;
                     }
                 });
         params.setBehavior(behavior);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 522de93..d67ac3b 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -21,6 +21,7 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
@@ -122,7 +123,7 @@
                 mCollapsingToolbarLayout.setTitle(mToolbarTitle);
             }
         }
-        disableCollapsingToolbarLayoutScrollingBehavior();
+        autoSetCollapsingToolbarLayoutScrolling();
     }
 
     /**
@@ -243,7 +244,7 @@
             mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
     }
 
-    private void disableCollapsingToolbarLayoutScrollingBehavior() {
+    private void autoSetCollapsingToolbarLayoutScrolling() {
         if (mAppBarLayout == null) {
             return;
         }
@@ -254,7 +255,9 @@
                 new AppBarLayout.Behavior.DragCallback() {
                     @Override
                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
-                        return false;
+                        // Header can be scrolling while device in landscape mode.
+                        return appBarLayout.getResources().getConfiguration().orientation
+                                == Configuration.ORIENTATION_LANDSCAPE;
                     }
                 });
         params.setBehavior(behavior);
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index b352b04..2887872 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,6 +1,6 @@
+set noparent
+
 chaohuiw@google.com
 hanxu@google.com
 kellyz@google.com
 pierreqian@google.com
-
-per-file *.xml = set noparent
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 8c97eca..d380136 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,9 @@
 
 buildscript {
     ext {
-        minSdk_version = 31
-        compose_version = '1.2.0-alpha04'
-        compose_material3_version = '1.0.0-alpha06'
+        spa_min_sdk = 31
+        jetpack_compose_version = '1.2.0-alpha04'
+        jetpack_compose_material3_version = '1.0.0-alpha06'
     }
 }
 plugins {
diff --git a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml b/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
index 9a89e5e..36b9313 100644
--- a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
@@ -13,14 +13,14 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib.spa.codelab">
 
     <application
         android:label="@string/app_name"
         android:supportsRtl="true"
-        android:theme="@style/Theme.SettingsLib.Compose.DayNight">
+        android:theme="@style/Theme.SpaLib.DayNight">
         <activity
             android:name="com.android.settingslib.spa.codelab.MainActivity"
             android:exported="true">
diff --git a/packages/SettingsLib/Spa/codelab/build.gradle b/packages/SettingsLib/Spa/codelab/build.gradle
index 5251ddd..169ecf0 100644
--- a/packages/SettingsLib/Spa/codelab/build.gradle
+++ b/packages/SettingsLib/Spa/codelab/build.gradle
@@ -25,7 +25,7 @@
 
     defaultConfig {
         applicationId "com.android.settingslib.spa.codelab"
-        minSdk minSdk_version
+        minSdk spa_min_sdk
         targetSdk 33
         versionCode 1
         versionName "1.0"
@@ -52,7 +52,7 @@
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
index 484b604..5c6b609 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
@@ -22,10 +22,10 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.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
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt
new file mode 100644
index 0000000..9fcbff8
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.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.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.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
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.Footer
+
+object FooterPageProvider : SettingsPageProvider {
+    override val name = "Footer"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        FooterPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = "Sample Footer"
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun FooterPage() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Preference(remember {
+            object : PreferenceModel {
+                override val title = "Some Preference"
+                override val summary = stateOf("Some summary")
+            }
+        })
+        Footer(footerText = "Footer text always at the end of page.")
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun FooterPagePreview() {
+    SettingsTheme {
+        FooterPage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
index 27c70e4..57a69c4 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
@@ -25,10 +25,10 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
 import com.android.settingslib.spa.codelab.R
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 object HomePageProvider : SettingsPageProvider {
     override val name = Destinations.Home
@@ -50,8 +50,11 @@
         )
 
         PreferencePageProvider.EntryItem()
-
+        SwitchPreferencePageProvider.EntryItem()
         ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+
+        SliderPageProvider.EntryItem()
+        FooterPageProvider.EntryItem()
     }
 }
 
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
index 862fc1e..54c588a 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
@@ -16,15 +16,24 @@
 
 package com.android.settingslib.spa.codelab.page
 
-import com.android.settingslib.spa.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
 
 object Destinations {
     const val Home = "Home"
     const val Preference = "Preference"
+    const val SwitchPreference = "SwitchPreference"
     const val Argument = "Argument"
+    const val Slider = "Slider"
 }
 
 val codelabPageRepository = SettingsPageRepository(
-    allPages = listOf(HomePageProvider, PreferencePageProvider, ArgumentPageProvider),
+    allPages = listOf(
+        HomePageProvider,
+        PreferencePageProvider,
+        SwitchPreferencePageProvider,
+        ArgumentPageProvider,
+        SliderPageProvider,
+        FooterPageProvider,
+    ),
     startDestination = Destinations.Home,
 )
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
index 81627f6..d53562d 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
@@ -30,10 +30,10 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.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
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import kotlinx.coroutines.delay
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
new file mode 100644
index 0000000..6e96581
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.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.ui.SettingsSlider
+import com.android.settingslib.spa.widget.ui.SettingsSliderModel
+
+object SliderPageProvider : SettingsPageProvider {
+    override val name = Destinations.Slider
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SliderPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = "Sample Slider"
+            override val onClick = navigator(Destinations.Slider)
+        })
+    }
+}
+
+@Composable
+private fun SliderPage() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider"
+            override val initValue = 40
+        })
+
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with icon"
+            override val initValue = 30
+            override val onValueChangeFinished = {
+                println("onValueChangeFinished")
+            }
+            override val icon = Icons.Outlined.AccessAlarm
+        })
+
+        val initValue = 0
+        var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+        var sliderPosition by remember { mutableStateOf(initValue) }
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with changeable icon"
+            override val initValue = initValue
+            override val onValueChange = { it: Int ->
+                sliderPosition = it
+                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+            }
+            override val onValueChangeFinished = {
+                println("onValueChangeFinished: the value is $sliderPosition")
+            }
+            override val icon = icon
+        })
+
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with steps"
+            override val initValue = 2
+            override val valueRange = 1..5
+            override val showSteps = true
+        })
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SliderPagePreview() {
+    SettingsTheme {
+        SliderPage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
new file mode 100644
index 0000000..b566afa
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.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
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import kotlinx.coroutines.delay
+
+object SwitchPreferencePageProvider : SettingsPageProvider {
+    override val name = Destinations.SwitchPreference
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SwitchPreferencePage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = "Sample SwitchPreference"
+            override val onClick = navigator(Destinations.SwitchPreference)
+        })
+    }
+}
+
+@Composable
+private fun SwitchPreferencePage() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        SampleSwitchPreference()
+        SampleSwitchPreferenceWithSummary()
+        SampleSwitchPreferenceWithAsyncSummary()
+        SampleNotChangeableSwitchPreference()
+    }
+}
+
+@Composable
+private fun SampleSwitchPreference() {
+    val checked = rememberSaveable { mutableStateOf(false) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithSummary() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = stateOf("With summary")
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithAsyncSummary() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    val summary = produceState(initialValue = " ") {
+        delay(1000L)
+        value = "Async summary"
+    }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = summary
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleNotChangeableSwitchPreference() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = stateOf("Not changeable")
+            override val changeable = stateOf(false)
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+    SettingsTheme {
+        SwitchPreferencePage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 463c076..3c8d91e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -31,6 +31,9 @@
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 60794c8..ad69da31 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -24,7 +24,7 @@
     compileSdk 33
 
     defaultConfig {
-        minSdk minSdk_version
+        minSdk spa_min_sdk
         targetSdk 33
     }
 
@@ -43,13 +43,13 @@
     }
     kotlinOptions {
         jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all"]
+        freeCompilerArgs = ["-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn"]
     }
     buildFeatures {
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
@@ -59,11 +59,11 @@
 }
 
 dependencies {
-    api "androidx.compose.material3:material3:$compose_material3_version"
-    api "androidx.compose.material:material-icons-extended:$compose_version"
-    api "androidx.compose.runtime:runtime-livedata:$compose_version"
-    api "androidx.compose.ui:ui-tooling-preview:$compose_version"
+    api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
+    api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
+    api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
+    api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api 'androidx.navigation:navigation-compose:2.5.0'
     api 'com.google.android.material:material:1.6.1'
-    debugApi "androidx.compose.ui:ui-tooling:$compose_version"
+    debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
index 8b52b50..67dd2b0 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
@@ -13,8 +13,8 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <resources>
 
-    <style name="Theme.SettingsLib.Compose.DayNight" />
+    <style name="Theme.SpaLib.DayNight" />
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 01f9ea5..e0e5fc2 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -13,15 +13,15 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <resources>
 
-    <style name="Theme.SettingsLib.Compose" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
     </style>
 
-    <style name="Theme.SettingsLib.Compose.DayNight">
+    <style name="Theme.SpaLib.DayNight">
         <item name="android:windowLightStatusBar">true</item>
     </style>
 </resources>
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/SpaActivity.kt
index 28a5899e..5b39b6e 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/SpaActivity.kt
@@ -24,9 +24,10 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.api.SettingsPageRepository
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 open class SpaActivity(
     private val settingsPageRepository: SettingsPageRepository,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
index 0ad0003..84daf22 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
similarity index 93%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
index ce39f4f..4a270b1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
 
 data class SettingsPageRepository(
     val allPages: List<SettingsPageProvider>,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
new file mode 100644
index 0000000..ae325f8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compose
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
+    Handler(Looper.getMainLooper())
+}
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+class DrawablePainter(
+    val drawable: Drawable
+) : Painter(), RememberObserver {
+    private var drawInvalidateTick by mutableStateOf(0)
+    private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+    private val callback: Drawable.Callback by lazy {
+        object : Drawable.Callback {
+            override fun invalidateDrawable(d: Drawable) {
+                // Update the tick so that we get re-drawn
+                drawInvalidateTick++
+                // Update our intrinsic size too
+                drawableIntrinsicSize = drawable.intrinsicSize
+            }
+
+            override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+                MAIN_HANDLER.postAtTime(what, time)
+            }
+
+            override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+                MAIN_HANDLER.removeCallbacks(what)
+            }
+        }
+    }
+
+    init {
+        if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+            // Update the drawable's bounds to match the intrinsic size
+            drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+        }
+    }
+
+    override fun onRemembered() {
+        drawable.callback = callback
+        drawable.setVisible(true, true)
+        if (drawable is Animatable) drawable.start()
+    }
+
+    override fun onAbandoned() = onForgotten()
+
+    override fun onForgotten() {
+        if (drawable is Animatable) drawable.stop()
+        drawable.setVisible(false, false)
+        drawable.callback = null
+    }
+
+    override fun applyAlpha(alpha: Float): Boolean {
+        drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+        return true
+    }
+
+    override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+        drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+        return true
+    }
+
+    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
+        drawable.setLayoutDirection(
+            when (layoutDirection) {
+                LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+                LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+            }
+        )
+
+    override val intrinsicSize: Size get() = drawableIntrinsicSize
+
+    override fun DrawScope.onDraw() {
+        drawIntoCanvas { canvas ->
+            // Reading this ensures that we invalidate when invalidateDrawable() is called
+            drawInvalidateTick
+
+            // Update the Drawable's bounds
+            drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+            canvas.withSave {
+                drawable.draw(canvas.nativeCanvas)
+            }
+        }
+    }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
+ * drawable contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from
+ * within Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
+    when (drawable) {
+        null -> EmptyPainter
+        is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap())
+        is ColorDrawable -> ColorPainter(Color(drawable.color))
+        // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+        // will receive the necessary events
+        else -> DrawablePainter(drawable.mutate())
+    }
+}
+
+private val Drawable.intrinsicSize: Size
+    get() = when {
+        // Only return a finite size if the drawable has an intrinsic size
+        intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+            Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+        }
+        else -> Size.Unspecified
+    }
+
+internal object EmptyPainter : Painter() {
+    override val intrinsicSize: Size get() = Size.Unspecified
+    override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
similarity index 96%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 35112ad..c68d5de 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.compositionLocalOf
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
similarity index 77%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
index 22d85e0..ba88546 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
@@ -14,11 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
 
+import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun <T> rememberContext(constructor: (Context) -> T): T {
+    val context = LocalContext.current
+    return remember(context) { constructor(context) }
+}
 
 /**
  * Remember the [State] initialized with the [this].
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 8cd9184..4626f0b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.dynamicDarkColorScheme
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
new file mode 100644
index 0000000..27fdc91
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.theme
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+data class SettingsColorScheme(
+    val background: Color = Color.Unspecified,
+    val categoryTitle: Color = Color.Unspecified,
+    val surface: Color = Color.Unspecified,
+    val surfaceHeader: Color = Color.Unspecified,
+    val secondaryText: Color = Color.Unspecified,
+    val primaryContainer: Color = Color.Unspecified,
+    val onPrimaryContainer: Color = Color.Unspecified,
+)
+
+internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
+
+@Composable
+internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme {
+    val context = LocalContext.current
+    return remember(isDarkTheme) {
+        when {
+            isDarkTheme -> dynamicDarkColorScheme(context)
+            else -> dynamicLightColorScheme(context)
+        }
+    }
+}
+
+/**
+ * Creates a light dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a
+ * light theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicLightColorScheme(context: Context): SettingsColorScheme {
+    val tonalPalette = dynamicTonalPalette(context)
+    return SettingsColorScheme(
+        background = tonalPalette.neutral95,
+        categoryTitle = tonalPalette.primary40,
+        surface = tonalPalette.neutral99,
+        surfaceHeader = tonalPalette.neutral90,
+        secondaryText = tonalPalette.neutralVariant30,
+        primaryContainer = tonalPalette.primary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+    )
+}
+
+/**
+ * Creates a dark dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a dark
+ * theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme {
+    val tonalPalette = dynamicTonalPalette(context)
+    return SettingsColorScheme(
+        background = tonalPalette.neutral10,
+        categoryTitle = tonalPalette.primary90,
+        surface = tonalPalette.neutral20,
+        surfaceHeader = tonalPalette.neutral30,
+        secondaryText = tonalPalette.neutralVariant80,
+        primaryContainer = tonalPalette.secondary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
similarity index 92%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 51e75c5..e1ca69b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.ui.unit.dp
 
 object SettingsDimension {
+    val itemIconSize = 24.dp
     val itemIconContainerSize = 72.dp
     val itemPaddingStart = 24.dp
     val itemPaddingEnd = 16.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
similarity index 92%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 5f4da8a..04ee3c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 object SettingsOpacity {
     const val Full = 1f
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
new file mode 100644
index 0000000..e6fa74e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+
+/**
+ * The Material 3 Theme for Settings.
+ */
+@Composable
+fun SettingsTheme(content: @Composable () -> Unit) {
+    val isDarkTheme = isSystemInDarkTheme()
+    val settingsColorScheme = settingsColorScheme(isDarkTheme)
+    val colorScheme = materialColorScheme(isDarkTheme).copy(
+        background = settingsColorScheme.background,
+    )
+
+    CompositionLocalProvider(LocalColorScheme provides settingsColorScheme(isDarkTheme)) {
+        MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+            content()
+        }
+    }
+}
+
+object SettingsTheme {
+    val colorScheme: SettingsColorScheme
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalColorScheme.current
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
new file mode 100644
index 0000000..f81f5e7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.R
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or
+ * tonal swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class SettingsTonalPalette(
+    // The neutral tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [neutral100] to the darkest shade [neutral0].
+    val neutral100: Color,
+    val neutral99: Color,
+    val neutral95: Color,
+    val neutral90: Color,
+    val neutral80: Color,
+    val neutral70: Color,
+    val neutral60: Color,
+    val neutral50: Color,
+    val neutral40: Color,
+    val neutral30: Color,
+    val neutral20: Color,
+    val neutral10: Color,
+    val neutral0: Color,
+
+    // The neutral variant tonal range, sometimes called "neutral 2",  from the
+    // generated dynamic color palette.
+    // Ordered from the lightest shade [neutralVariant100] to the darkest shade [neutralVariant0].
+    val neutralVariant100: Color,
+    val neutralVariant99: Color,
+    val neutralVariant95: Color,
+    val neutralVariant90: Color,
+    val neutralVariant80: Color,
+    val neutralVariant70: Color,
+    val neutralVariant60: Color,
+    val neutralVariant50: Color,
+    val neutralVariant40: Color,
+    val neutralVariant30: Color,
+    val neutralVariant20: Color,
+    val neutralVariant10: Color,
+    val neutralVariant0: Color,
+
+    // The primary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [primary100] to the darkest shade [primary0].
+    val primary100: Color,
+    val primary99: Color,
+    val primary95: Color,
+    val primary90: Color,
+    val primary80: Color,
+    val primary70: Color,
+    val primary60: Color,
+    val primary50: Color,
+    val primary40: Color,
+    val primary30: Color,
+    val primary20: Color,
+    val primary10: Color,
+    val primary0: Color,
+
+    // The secondary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [secondary100] to the darkest shade [secondary0].
+    val secondary100: Color,
+    val secondary99: Color,
+    val secondary95: Color,
+    val secondary90: Color,
+    val secondary80: Color,
+    val secondary70: Color,
+    val secondary60: Color,
+    val secondary50: Color,
+    val secondary40: Color,
+    val secondary30: Color,
+    val secondary20: Color,
+    val secondary10: Color,
+    val secondary0: Color,
+
+    // The tertiary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [tertiary100] to the darkest shade [tertiary0].
+    val tertiary100: Color,
+    val tertiary99: Color,
+    val tertiary95: Color,
+    val tertiary90: Color,
+    val tertiary80: Color,
+    val tertiary70: Color,
+    val tertiary60: Color,
+    val tertiary50: Color,
+    val tertiary40: Color,
+    val tertiary30: Color,
+    val tertiary20: Color,
+    val tertiary10: Color,
+    val tertiary0: Color,
+)
+
+/** Dynamic colors in Material. */
+internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette(
+    // The neutral tonal range from the generated dynamic color palette.
+    neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
+    neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
+    neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
+    neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
+    neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
+    neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
+    neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
+    neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
+    neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
+    neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
+    neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
+    neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
+    neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
+
+    // The neutral variant tonal range, sometimes called "neutral 2",  from the
+    // generated dynamic color palette.
+    neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
+    neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
+    neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
+    neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
+    neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
+    neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
+    neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
+    neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
+    neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
+    neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
+    neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
+    neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
+    neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
+
+    // The primary tonal range from the generated dynamic color palette.
+    primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
+    primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
+    primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
+    primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
+    primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
+    primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
+    primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
+    primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
+    primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
+    primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
+    primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
+    primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
+    primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
+
+    // The secondary tonal range from the generated dynamic color palette.
+    secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
+    secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
+    secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
+    secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
+    secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
+    secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
+    secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
+    secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
+    secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
+    secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
+    secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
+    secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
+    secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
+
+    // The tertiary tonal range from the generated dynamic color palette.
+    tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
+    tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
+    tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
+    tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
+    tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
+    tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
+    tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
+    tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
+    tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
+    tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
+    tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
+    tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
+    tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
+)
+
+private object ColorResourceHelper {
+    @DoNotInline
+    fun getColor(context: Context, @ColorRes id: Int): Color {
+        return Color(context.resources.getColor(id, context.theme))
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
new file mode 100644
index 0000000..07f09ba
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+
+private class SettingsTypography {
+    private val brand = FontFamily.Default
+    private val plain = FontFamily.Default
+
+    val typography = Typography(
+        displayLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 57.sp,
+            lineHeight = 64.sp,
+            letterSpacing = (-0.2).sp
+        ),
+        displayMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 45.sp,
+            lineHeight = 52.sp,
+            letterSpacing = 0.0.sp
+        ),
+        displaySmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 36.sp,
+            lineHeight = 44.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 32.sp,
+            lineHeight = 40.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 28.sp,
+            lineHeight = 36.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineSmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 24.sp,
+            lineHeight = 32.sp,
+            letterSpacing = 0.0.sp
+        ),
+        titleLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 22.sp,
+            lineHeight = 28.sp,
+            letterSpacing = 0.02.em,
+        ),
+        titleMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 20.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.02.em,
+        ),
+        titleSmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 18.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.02.em,
+        ),
+        bodyLarge = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 16.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.01.em,
+        ),
+        bodyMedium = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 14.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.01.em,
+        ),
+        bodySmall = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 12.sp,
+            lineHeight = 16.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelLarge = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 16.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelMedium = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 14.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelSmall = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 12.sp,
+            lineHeight = 16.sp,
+            letterSpacing = 0.01.em,
+        ),
+    )
+}
+
+@Composable
+internal fun rememberSettingsTypography(): Typography {
+    return remember { SettingsTypography().typography }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
deleted file mode 100644
index fce9f2b..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
+++ /dev/null
@@ -1,34 +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.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-
-/**
- * The Material 3 Theme for Settings.
- */
-@Composable
-fun SettingsTheme(content: @Composable () -> Unit) {
-    val isDarkTheme = isSystemInDarkTheme()
-    val colorScheme = materialColorScheme(isDarkTheme)
-
-    MaterialTheme(colorScheme = colorScheme) {
-        content()
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index d415e9b..9a34dbf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -25,8 +25,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.Divider
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
@@ -35,10 +33,11 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsOpacity
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsTitle
 
 @Composable
 internal fun BaseLayout(
@@ -94,11 +93,7 @@
 @Composable
 private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
     Column(modifier) {
-        Text(
-            text = title,
-            color = MaterialTheme.colorScheme.onSurface,
-            style = MaterialTheme.typography.titleMedium,
-        )
+        SettingsTitle(title)
         subTitle()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 563a47a..4b2c8e4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -19,16 +19,15 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.BatteryChargingFull
 import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsBody
 
 @Composable
 internal fun BasePreference(
@@ -44,15 +43,7 @@
 ) {
     BaseLayout(
         title = title,
-        subTitle = {
-            if (summary.value.isNotEmpty()) {
-                Text(
-                    text = summary.value,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant,
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-            }
-        },
+        subTitle = { SettingsBody(summary) },
         modifier = modifier,
         icon = icon,
         enabled = enabled,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index ee20280..1a80ed2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -20,7 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.stateOf
+import com.android.settingslib.spa.framework.compose.stateOf
 
 /**
  * The widget model for [Preference] widget.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
new file mode 100644
index 0000000..0dab0df
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.preference
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsSwitch
+
+/**
+ * The widget model for [SwitchPreference] widget.
+ */
+interface SwitchPreferenceModel {
+    /**
+     * The title of this [SwitchPreference].
+     */
+    val title: String
+
+    /**
+     * The summary of this [SwitchPreference].
+     */
+    val summary: State<String>
+        get() = stateOf("")
+
+    /**
+     * Indicates whether this [SwitchPreference] is checked.
+     *
+     * This can be `null` during the data loading before the data is available.
+     */
+    val checked: State<Boolean?>
+
+    /**
+     * Indicates whether this [SwitchPreference] is changeable.
+     *
+     * Not changeable [SwitchPreference] will be displayed in disabled style.
+     */
+    val changeable: State<Boolean>
+        get() = stateOf(true)
+
+    /**
+     * The switch change handler of this [SwitchPreference].
+     *
+     * If `null`, this [SwitchPreference] is not [toggleable].
+     */
+    val onCheckedChange: ((newChecked: Boolean) -> Unit)?
+}
+
+/**
+ * SwitchPreference widget.
+ *
+ * Data is provided through [SwitchPreferenceModel].
+ */
+@Composable
+fun SwitchPreference(model: SwitchPreferenceModel) {
+    InternalSwitchPreference(
+        title = model.title,
+        summary = model.summary,
+        checked = model.checked,
+        changeable = model.changeable,
+        onCheckedChange = model.onCheckedChange,
+    )
+}
+
+@Composable
+internal fun InternalSwitchPreference(
+    title: String,
+    summary: State<String> = "".toState(),
+    checked: State<Boolean?>,
+    changeable: State<Boolean> = true.toState(),
+    paddingStart: Dp = SettingsDimension.itemPaddingStart,
+    paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
+    paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
+    onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+    val checkedValue = checked.value
+    val indication = LocalIndication.current
+    val modifier = remember(checkedValue) {
+        if (checkedValue != null && onCheckedChange != null) {
+            Modifier.toggleable(
+                value = checkedValue,
+                interactionSource = MutableInteractionSource(),
+                indication = indication,
+                enabled = changeable.value,
+                role = Role.Switch,
+                onValueChange = onCheckedChange,
+            )
+        } else Modifier
+    }
+    BasePreference(
+        title = title,
+        summary = summary,
+        modifier = modifier,
+        enabled = changeable,
+        paddingStart = paddingStart,
+        paddingEnd = paddingEnd,
+        paddingVertical = paddingVertical,
+    ) {
+        SettingsSwitch(checked = checked, changeable = changeable)
+    }
+}
+
+@Preview
+@Composable
+private fun SwitchPreferencePreview() {
+    SettingsTheme {
+        Column {
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                checked = true.toState(),
+                onCheckedChange = {},
+            )
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                summary = "Summary".toState(),
+                checked = false.toState(),
+                onCheckedChange = {},
+            )
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
new file mode 100644
index 0000000..41fd03b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun Footer(footerText: String) {
+    if (footerText.isEmpty()) return
+    Column(Modifier.padding(SettingsDimension.itemPadding)) {
+        SettingsIcon(imageVector = Icons.Outlined.Info, contentDescription = null)
+        Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+        SettingsBody(footerText)
+    }
+}
+
+@Preview
+@Composable
+private fun FooterPreview() {
+    SettingsTheme {
+        Footer("Footer text always at the end of page.")
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
new file mode 100644
index 0000000..cb08cdb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsIcon(
+    imageVector: ImageVector,
+    contentDescription: String?,
+) {
+    Icon(
+        imageVector = imageVector,
+        contentDescription = contentDescription,
+        modifier = Modifier.size(SettingsDimension.itemIconSize),
+        tint = MaterialTheme.colorScheme.onSurface,
+    )
+}
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/ui/SettingsSlider.kt
new file mode 100644
index 0000000..0454ac3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.BaseLayout
+import kotlin.math.roundToInt
+
+/**
+ * The widget model for [SettingsSlider] widget.
+ */
+interface SettingsSliderModel {
+    /**
+     * The title of this [SettingsSlider].
+     */
+    val title: String
+
+    /**
+     * The initial position of the [SettingsSlider].
+     */
+    val initValue: Int
+
+    /**
+     * The value range for this [SettingsSlider].
+     */
+    val valueRange: IntRange
+        get() = 0..100
+
+    /**
+     * The lambda to be invoked during the value change by dragging or a click. This callback is
+     * used to get the real time value of the [SettingsSlider].
+     */
+    val onValueChange: ((value: Int) -> Unit)?
+        get() = null
+
+    /**
+     * The lambda to be invoked when value change has ended. This callback is used to get when the
+     * user has completed selecting a new value by ending a drag or a click.
+     */
+    val onValueChangeFinished: (() -> Unit)?
+        get() = null
+
+    /**
+     * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
+     */
+    val icon: ImageVector?
+        get() = null
+
+    /**
+     * Indicates whether to show step marks. If show step marks, when user finish sliding,
+     * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
+     * the step marks by default.
+     *
+     * The step is fixed to 1.
+     */
+    val showSteps: Boolean
+        get() = false
+}
+
+/**
+ * Settings slider widget.
+ *
+ * Data is provided through [SettingsSliderModel].
+ */
+@Composable
+fun SettingsSlider(model: SettingsSliderModel) {
+    SettingsSlider(
+        title = model.title,
+        initValue = model.initValue,
+        valueRange = model.valueRange,
+        onValueChange = model.onValueChange,
+        onValueChangeFinished = model.onValueChangeFinished,
+        icon = model.icon,
+        showSteps = model.showSteps,
+    )
+}
+
+@Composable
+internal fun SettingsSlider(
+    title: String,
+    initValue: Int,
+    valueRange: IntRange = 0..100,
+    onValueChange: ((value: Int) -> Unit)? = null,
+    onValueChangeFinished: (() -> Unit)? = null,
+    icon: ImageVector? = null,
+    showSteps: Boolean = false,
+    modifier: Modifier = Modifier,
+) {
+    var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
+    BaseLayout(
+        title = title,
+        subTitle = {
+            Slider(
+                value = sliderPosition,
+                onValueChange = {
+                    sliderPosition = it
+                    onValueChange?.invoke(sliderPosition.roundToInt())
+                },
+                modifier = modifier,
+                valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
+                steps = if (showSteps) (valueRange.count() - 2) else 0,
+                onValueChangeFinished = onValueChangeFinished,
+            )
+        },
+        icon = if (icon != null) ({
+            Icon(imageVector = icon, contentDescription = null)
+        }) else null,
+    )
+}
+
+@Preview
+@Composable
+private fun SettingsSliderPreview() {
+    SettingsTheme {
+        val initValue = 30
+        var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
+        SettingsSlider(
+            title = "Alarm Volume",
+            initValue = 30,
+            onValueChange = { sliderPosition = it },
+            onValueChangeFinished = {
+                println("onValueChangeFinished: the value is $sliderPosition")
+            },
+            icon = Icons.Outlined.AccessAlarm,
+        )
+    }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderIconChangePreview() {
+    SettingsTheme {
+        var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
+        SettingsSlider(
+            title = "Media Volume",
+            initValue = 40,
+            onValueChange = { it: Int ->
+                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+            },
+            icon = icon,
+        )
+    }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderStepsPreview() {
+    SettingsTheme {
+        SettingsSlider(
+            title = "Display Text",
+            initValue = 2,
+            valueRange = 1..5,
+            showSteps = true,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
new file mode 100644
index 0000000..45d5f6b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsSwitch(
+    checked: State<Boolean?>,
+    changeable: State<Boolean>,
+    onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
+) {
+    // TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
+    //       updated to date.
+    val checkedValue = checked.value
+    if (checkedValue != null) {
+        Checkbox(
+            checked = checkedValue,
+            onCheckedChange = onCheckedChange,
+            enabled = changeable.value,
+        )
+    } else {
+        Checkbox(
+            checked = false,
+            onCheckedChange = null,
+            enabled = false,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
new file mode 100644
index 0000000..a414c89
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@Composable
+fun SettingsTitle(title: State<String>) {
+    SettingsTitle(title.value)
+}
+
+@Composable
+fun SettingsTitle(title: String) {
+    Text(
+        text = title,
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.titleMedium,
+    )
+}
+
+@Composable
+fun SettingsBody(body: State<String>) {
+    SettingsBody(body.value)
+}
+
+@Composable
+fun SettingsBody(body: String) {
+    if (body.isNotEmpty()) {
+        Text(
+            text = body,
+            color = MaterialTheme.colorScheme.onSurfaceVariant,
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 707017e..be5a5ec 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -24,7 +24,7 @@
     compileSdk 33
 
     defaultConfig {
-        minSdk minSdk_version
+        minSdk spa_min_sdk
         targetSdk 33
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +50,7 @@
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
@@ -62,6 +62,6 @@
 dependencies {
     androidTestImplementation(project(":spa"))
     androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
-    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
+    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 4097946..a92f871 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -26,7 +26,7 @@
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.toState
+import com.android.settingslib.spa.framework.compose.toState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
new file mode 100644
index 0000000..d6c8fbc
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+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.android.settingslib.spa.framework.compose.stateOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
+    }
+
+    @Test
+    fun toggleable_initialStateIsCorrect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+
+    @Test
+    fun click_changeable_withEffect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").performClick()
+        composeTestRule.onNode(isToggleable()).assertIsOn()
+    }
+
+    @Test
+    fun click_notChangeable_noEffect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = false)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").performClick()
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+}
+
+@Composable
+private fun TestSwitchPreference(changeable: Boolean) {
+    val checked = rememberSaveable { mutableStateOf(false) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val changeable = stateOf(changeable)
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
new file mode 100644
index 0000000..48f7ff2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -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.
+//
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SpaPrivilegedLib",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "SpaLib",
+        "SettingsLib",
+        "androidx.compose.runtime_runtime",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    min_sdk_version: "31",
+}
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
new file mode 100644
index 0000000..2efa107
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest package="com.android.settingslib.spaprivileged" />
diff --git a/packages/SettingsLib/SpaPrivileged/OWNERS b/packages/SettingsLib/SpaPrivileged/OWNERS
new file mode 100644
index 0000000..9256ca5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/packages/SettingsLib/Spa/OWNERS
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
new file mode 100644
index 0000000..a6378ef
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.compose.rememberContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
+
+interface AppRepository {
+    @Composable
+    fun produceLabel(app: ApplicationInfo): State<String>
+
+    @Composable
+    fun produceIcon(app: ApplicationInfo): State<Drawable?>
+}
+
+private class AppRepositoryImpl(private val context: Context) : AppRepository {
+    private val packageManager = context.packageManager
+
+    @Composable
+    override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
+        withContext(Dispatchers.Default) {
+            value = app.loadLabel(packageManager).toString()
+        }
+    }
+
+    @Composable
+    override fun produceIcon(app: ApplicationInfo) =
+        produceState<Drawable?>(initialValue = null, app) {
+            withContext(Dispatchers.Default) {
+                value = Utils.getBadgedIcon(context, app)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
index 7c9df10..5a3e666 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.settingslib.spaprivileged.framework.app
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+
+object PackageManagers {
+    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
+        PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
new file mode 100644
index 0000000..5ae514c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
+
+@Composable
+fun AppInfo(packageName: String, userId: Int) {
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(16.dp),
+        horizontalAlignment = Alignment.CenterHorizontally) {
+        val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
+        Box(modifier = Modifier.padding(8.dp)) {
+            AppIcon(app = packageInfo.applicationInfo, size = 48)
+        }
+        AppLabel(packageInfo.applicationInfo)
+        Spacer(modifier = Modifier.height(4.dp))
+        SettingsBody(packageInfo.versionName)
+    }
+}
+
+@Composable
+fun AppIcon(app: ApplicationInfo, size: Int) {
+    val appRepository = rememberAppRepository()
+    Image(
+        painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
+        contentDescription = null,
+        modifier = Modifier.size(size.dp)
+    )
+}
+
+@Composable
+fun AppLabel(app: ApplicationInfo) {
+    val appRepository = rememberAppRepository()
+    SettingsTitle(appRepository.produceLabel(app))
+}
diff --git a/packages/SettingsLib/res/values-af/arrays.xml b/packages/SettingsLib/res/values-af/arrays.xml
index 1de7668..a7e44d3 100644
--- a/packages/SettingsLib/res/values-af/arrays.xml
+++ b/packages/SettingsLib/res/values-af/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gebruik stelselkeuse (verstek)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gebruik stelselkeuse (verstek)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gebruik stelselkeuse (verstek)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index fe8030f..0b23dd1 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luggehalte"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Uitsaai-inligting"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Huiskontroles"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Kies \'n profielprent"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Verstekgebruikerikoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fisieke sleutelbord"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3c034f..2bf9cb2 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"የአየር ሁኔታ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"የአየር ጥራት"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"የCast መረጃ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"የቤት ውስጥ ቁጥጥሮች"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"የመገለጫ ሥዕል ይምረጡ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ነባሪ የተጠቃሚ አዶ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 6783654..6c71d9c 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"الطقس"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"جودة الهواء"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"معلومات البث"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"إدارة آلية للمنزل"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"اختيار صورة الملف الشخصي"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"رمز المستخدم التلقائي"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 543c703..299df77 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -121,7 +121,7 @@
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"সম্পৰ্কসূচী আৰু কলৰ ইতিহাস শ্বেয়াৰ কৰাৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ইণ্টাৰনেট সংযোগ শ্বেয়াৰ"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"পাঠ বাৰ্তা"</string>
-    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিম প্ৰৱেশ"</string>
+    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string>
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"বতৰ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"বায়ুৰ গুণগত মান"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাষ্টৰ তথ্য"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"গৃহ নিয়ন্ত্ৰণ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string>
diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml
index 48974a7..d1f157a 100644
--- a/packages/SettingsLib/res/values-az/arrays.xml
+++ b/packages/SettingsLib/res/values-az/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sistem Seçimini istifadə edin (Defolt)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sistem Seçimini istifadə edin (Defolt)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sistem Seçimini istifadə edin (Defolt)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index f3e5d95..fd79192 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hava"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havanın keyfiyyəti"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayım məlumatı"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Əsas səhifə kontrolları"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil şəkli seçin"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Defolt istifadəçi ikonası"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziki klaviatura"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index 337da26..63b08fa 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Koristi izbor sistema (podrazumevano)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Koristi izbor sistema (podrazumevano)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Koristi izbor sistema (podrazumevano)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 6a880aa..f7d9be9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vreme"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet vazduha"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o prebacivanju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravljanje domom"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Podrazumevana ikona korisnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index a902426..431bcd5 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Надвор\'е"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якасць паветра"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Даныя пра трансляцыю"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Кіраванне домам"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Выберыце відарыс профілю"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Стандартны карыстальніцкі значок"</string>
diff --git a/packages/SettingsLib/res/values-bg/arrays.xml b/packages/SettingsLib/res/values-bg/arrays.xml
index 1aad6ca7..849e694 100644
--- a/packages/SettingsLib/res/values-bg/arrays.xml
+++ b/packages/SettingsLib/res/values-bg/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Използване на сист. избор (стандартно)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"Разширено аудиокодиране (AAC)"</item>
+    <item msgid="1049450003868150455">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Използване на сист. избор (стандартно)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"Разширено аудиокодиране (AAC)"</item>
+    <item msgid="8627333814413492563">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Използване на сист. избор (стандартно)"</item>
     <item msgid="8003118270854840095">"44,1 кХц"</item>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index c512366..cbc4322 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Времето"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество на въздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Предаване: Инф."</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за дома"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Изберете снимка на потребителския профил"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Икона за основния потребител"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физическа клавиатура"</string>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index 5e6bb95..a3bc4fd 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
     <item msgid="8003118270854840095">"৪৪.১ kHz"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 23eed04..3394eea 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"এয়ার কোয়ালিটি"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাস্ট সম্পর্কিত তথ্য"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"হোম কন্ট্রোল"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"একটি প্রোফাইল ছবি বেছে নিন"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ডিফল্ট ব্যবহারকারীর আইকন"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ফিজিক্যাল কীবোর্ড"</string>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index 262a35f..926ad84 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Korištenje odabira sistema (zadano)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Korištenje odabira sistema (zadano)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Korištenje odabira sistema (zadano)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 78a484f..1547e83 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -265,7 +265,7 @@
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, otklanjanje grešaka, programer"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"Prečica za izvještaj o greškama"</string>
     <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Vidite dugme za prijavu grešaka u meniju napajanja"</string>
-    <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj ekran"</string>
+    <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj"</string>
     <string name="keep_screen_on_summary" msgid="1510731514101925829">"Ekran neće prelaziti u stanje mirovanja tokom punjenja"</string>
     <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Omogući Bluetooth HCI snoop zapis"</string>
     <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Snimite Bluetooth pakete. (Uključite/isključite Bluetooth nakon što promijenite ovu postavku)"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o emitiranju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrole doma"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Zadana ikona korisnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index 8c34a1f..b6f1590 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utilitza la selecció del sistema (predeterminada)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utilitza la selecció del sistema (predeterminada)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utilitza la selecció del sistema (predeterminada)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 606a379..c97a6c1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Temps"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualitat de l\'aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informació d\'emissió"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domòtica"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"D\'una ullada"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Tria una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona d\'usuari predeterminat"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclat físic"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 37c0bb4..dfd6d61 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Počasí"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info o odesílání"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládání domácnosti"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Vyberte profilový obrázek"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Výchozí uživatelská ikona"</string>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 155104ae..48a33f6 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Brug systemvalg (standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Brug systemvalg (standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Brug systemvalg (standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d80e1ec..790819c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vejr"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast-oplysninger"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Styring af hjem"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Overblik"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Vælg et profilbillede"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon for standardbruger"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml
index 31126a8..ca999db 100644
--- a/packages/SettingsLib/res/values-de/arrays.xml
+++ b/packages/SettingsLib/res/values-de/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Systemauswahl verwenden (Standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Systemauswahl verwenden (Standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Systemauswahl verwenden (Standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d4fafe1..bd919f4 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Wetter"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftqualität"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Streaming-Info"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Smart-Home-Steuerung"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profilbild auswählen"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standardmäßiges Nutzersymbol"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physische Tastatur"</string>
diff --git a/packages/SettingsLib/res/values-el/arrays.xml b/packages/SettingsLib/res/values-el/arrays.xml
index 70000e1..b95f6fc 100644
--- a/packages/SettingsLib/res/values-el/arrays.xml
+++ b/packages/SettingsLib/res/values-el/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index aa613a5..9326dac 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Καιρός"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ποιότητα αέρα"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Πληροφορίες ηθοποιών"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Οικιακοί έλεγχοι"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Επιλογή φωτογραφίας προφίλ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Προεπιλεγμένο εικονίδιο χρήστη"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Φυσικό πληκτρολόγιο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/arrays.xml b/packages/SettingsLib/res/values-en-rAU/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rAU/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rAU/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 8650b77..bf20fcc 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/arrays.xml b/packages/SettingsLib/res/values-en-rGB/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rGB/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rGB/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/arrays.xml b/packages/SettingsLib/res/values-en-rIN/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rIN/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rIN/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/arrays.xml b/packages/SettingsLib/res/values-en-rXC/arrays.xml
index 34db380..8af0a4a 100644
--- a/packages/SettingsLib/res/values-en-rXC/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rXC/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎map13‎‏‎‎‏‎"</item>
     <item msgid="8147982633566548515">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎map14‎‏‎‎‏‎"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‎Use System Selection (Default)‎‏‎‎‏‎"</item>
+    <item msgid="4055460186095649420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎SBC‎‏‎‎‏‎"</item>
+    <item msgid="720249083677397051">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎AAC‎‏‎‎‏‎"</item>
+    <item msgid="1049450003868150455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX">aptX™</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="2908219194098827570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX_HD">aptX™ HD</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="3825367753087348007">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎LDAC‎‏‎‎‏‎"</item>
+    <item msgid="328951785723550863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎LC3‎‏‎‎‏‎"</item>
+    <item msgid="506175145534048710">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎Opus‎‏‎‎‏‎"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎Use System Selection (Default)‎‏‎‎‏‎"</item>
+    <item msgid="9024885861221697796">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎SBC‎‏‎‎‏‎"</item>
+    <item msgid="4688890470703790013">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎AAC‎‏‎‎‏‎"</item>
+    <item msgid="8627333814413492563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX">aptX™</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="3517061573669307965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX_HD">aptX™ HD</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="2553206901068987657">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎LDAC‎‏‎‎‏‎"</item>
+    <item msgid="3940992993241040716">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‎LC3‎‏‎‎‏‎"</item>
+    <item msgid="7940970833006181407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‏‎Opus‎‏‎‎‏‎"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎Use System Selection (Default)‎‏‎‎‏‎"</item>
     <item msgid="8003118270854840095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎44.1 kHz‎‏‎‎‏‎"</item>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 21697b9..21c1bb7 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎Air Quality‎‏‎‎‏‎"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎Cast Info‎‏‎‎‏‎"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎Home Controls‎‏‎‎‏‎"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎Smartspace‎‏‎‎‏‎"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‎Choose a profile picture‎‏‎‎‏‎"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎Default user icon‎‏‎‎‏‎"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎Physical keyboard‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 27cd0ab..8821f42 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -661,6 +661,8 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info de reparto"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Control de la casa"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
+    <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícono de usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index 0677864..4924407 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar preferencia del sistema (predeterminado)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar preferencia del sistema (predeterminado)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar preferencia del sistema (predeterminado)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index d221c8d..cea4835 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Tiempo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de emisión"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domótica"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"De un vistazo"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una imagen de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icono de usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml
index d986ecf..0402ac2 100644
--- a/packages/SettingsLib/res/values-et/arrays.xml
+++ b/packages/SettingsLib/res/values-et/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Süsteemi valiku kasutamine (vaikeseade)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index f32ae23..13fd319 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ilm"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Õhukvaliteet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Osatäitjate teave"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodu juhtimine"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Ülevaade"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Valige profiilipilt"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Vaikekasutajaikoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Füüsiline klaviatuur"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index d166e1b..9c12e95 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Erabili sistema-hautapena (lehenetsia)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Erabili sistema-hautapena (lehenetsia)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Erabili sistema-hautapena (lehenetsia)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index bcb57e8..3c96b6f 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Eguraldia"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Airearen kalitatea"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Igorpenari buruzko informazioa"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Etxeko gailuak kontrolatzeko aukerak"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Aukeratu profileko argazki bat"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Erabiltzaile lehenetsiaren ikonoa"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teklatu fisikoa"</string>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index b7761dd..41410cb 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
     <item msgid="8003118270854840095">"۴۴٫۱ کیلوهرتز"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 6abe873..5190e0f 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"آب‌وهوا"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"کیفیت هوا"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"اطلاعات پخش محتوا"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"کنترل لوازم خانگی"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"انتخاب عکس نمایه"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"نماد کاربر پیش‌فرض"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"صفحه‌کلید فیزیکی"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 2f3436e..c50a2e0 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Sää"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ilmanlaatu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Striimaustiedot"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodin ohjaus"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Valitse profiilikuva"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Oletuskäyttäjäkuvake"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/arrays.xml b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
index 12acbb6..808c3f2 100644
--- a/packages/SettingsLib/res/values-fr-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utiliser sélect. du système (par défaut)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utiliser sélect. du système (par défaut)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utiliser sélect. du système (par défaut)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 28b3cd9..b8b4659 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info diffusion"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Aperçu"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choisir une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icône d\'utilisateur par défaut"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml
index 80ac7e4..92546da 100644
--- a/packages/SettingsLib/res/values-fr/arrays.xml
+++ b/packages/SettingsLib/res/values-fr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utiliser la sélection du système (par défaut)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utiliser la sélection du système (par défaut)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utiliser la sélection du système (par défaut)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index da1497a..4a2d01e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Infos distribution"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choisissez une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icône de l\'utilisateur par défaut"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml
index b6cf48e..f663120 100644
--- a/packages/SettingsLib/res/values-gl/arrays.xml
+++ b/packages/SettingsLib/res/values-gl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar selección do sistema (predeterminado)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usa a selección do sistema (predeterminado)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar selección do sistema (predeterminado)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 7822749..93f9fcf 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -662,6 +662,7 @@
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Datos da emisión"</string>
     <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
     <skip />
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espazo intelixente"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolle unha imaxe do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona do usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 7e668e7..e527d81 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index cebb1fe..0e19125 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"હવામાન"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"હવાની ક્વૉલિટી"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"કાસ્ટ વિશેની માહિતી"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ઘરેલુ સાધન નિયંત્રણો"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"પ્રોફાઇલ ફોટો પસંદ કરો"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ડિફૉલ્ટ વપરાશકર્તાનું આઇકન"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ભૌતિક કીબોર્ડ"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 13da75b..9b8d83e 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टम से चुने जाने का इस्तेमाल करें (डिफ़ॉल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 417d2b2..ed0a771 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवा की क्वालिटी"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टिंग की जानकारी"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफ़ाइल फ़ोटो चुनें"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"उपयोगकर्ता के लिए डिफ़ॉल्ट आइकॉन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"फ़िज़िकल कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 267000f..af26040 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvaliteta zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Inform. o emitiranju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravlj. kuć. uređ."</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odabir profilne slike"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona zadanog korisnika"</string>
diff --git a/packages/SettingsLib/res/values-hu/arrays.xml b/packages/SettingsLib/res/values-hu/arrays.xml
index a5f37ea..409d600 100644
--- a/packages/SettingsLib/res/values-hu/arrays.xml
+++ b/packages/SettingsLib/res/values-hu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Rendszerérték (alapértelmezett)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Rendszerérték (alapértelmezett)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Rendszerérték (alapértelmezett)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 69ddad8..3db43e7 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Időjárás"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Levegőminőség"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Átküldési információ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Otthonvezérlés"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profilkép választása"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Alapértelmezett felhasználó ikonja"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizikai billentyűzet"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index d8efbd3..8971fce 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Եղանակ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Օդի որակը"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Հեռարձակման տվյալներ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Տան կարգավորումներ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Պրոֆիլի նկար ընտրեք"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Օգտատիրոջ կանխադրված պատկերակ"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 5b0ad98..9527417 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gunakan Pilihan Sistem (Default)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 029ad8b..6185fd3 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualitas Udara"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info Transmisi"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrol Rumah"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pilih foto profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna default"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Keyboard fisik"</string>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index 9d481f8..0b5b978 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Nota val kerfisins (sjálfgefið)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Nota val kerfisins (sjálfgefið)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Nota val kerfisins (sjálfgefið)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ccf06b0..e718786 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Veður"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Loftgæði"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Útsendingaruppl."</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Heimastýringar"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Veldu prófílmynd"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Tákn sjálfgefins notanda"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Vélbúnaðarlyklaborð"</string>
diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml
index 62450da3..ae1e515 100644
--- a/packages/SettingsLib/res/values-it/arrays.xml
+++ b/packages/SettingsLib/res/values-it/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usa selezione di sistema (predefinita)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usa selezione di sistema (predefinita)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usa selezione di sistema (predefinita)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 3811152..2e9b202 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -117,7 +117,7 @@
     <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Trasferimento file"</string>
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo di input"</string>
     <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Accesso a Internet"</string>
-    <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivis. contatti e cronologia chiamate"</string>
+    <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivisione contatti e cronologia chiamate"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Usa per condivisione di contatti e cronologia chiamate"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Condivisione connessione Internet"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"SMS"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualità dell\'aria"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info sul cast"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlli della casa"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Scegli un\'immagine del profilo"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona dell\'utente predefinito"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fisica"</string>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index 49f3fcf..5d72aff 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
     <item msgid="8003118270854840095">"44.1 קילו-הרץ"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 3421fb5..7e9fd89 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"איכות האוויר"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"פרטי ההעברה"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"בית חכם"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"בחירה של תמונת פרופיל"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"סמל המשתמש שמוגדר כברירת מחדל"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"מקלדת פיזית"</string>
diff --git a/packages/SettingsLib/res/values-ja/arrays.xml b/packages/SettingsLib/res/values-ja/arrays.xml
index d73cc43..775e31c 100644
--- a/packages/SettingsLib/res/values-ja/arrays.xml
+++ b/packages/SettingsLib/res/values-ja/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"システムの選択(デフォルト)を使用"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"システムの選択(デフォルト)を使用"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"システムの選択(デフォルト)を使用"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index e7d2cf8..a3311f5 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天気"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"大気質"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"キャスト情報"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"スマートホーム"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"プロフィール写真の選択"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"デフォルト ユーザー アイコン"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"物理キーボード"</string>
diff --git a/packages/SettingsLib/res/values-ka/arrays.xml b/packages/SettingsLib/res/values-ka/arrays.xml
index c0d6f97..f3545b6 100644
--- a/packages/SettingsLib/res/values-ka/arrays.xml
+++ b/packages/SettingsLib/res/values-ka/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
     <item msgid="8003118270854840095">"44,1 კჰც"</item>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 09f5471..b3e4402 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ჰაერის ხარისხი"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ტრანსლირების ინფო"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"სახლის მართვა"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ჭკვიანი სივრცე"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"აირჩიეთ პროფილის სურათი"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"მომხმარებლის ნაგულისხმევი ხატულა"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ფიზიკური კლავიატურა"</string>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index fc998e7..9971f86 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Жүйенің таңдағанын алу (әдепкі)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"L34C"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Жүйенің таңдағанын алу (әдепкі)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"L34C"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Жүйенің таңдағанын алу (әдепкі)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 3281303..5ecba9b 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ауа райы"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ауа сапасы"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Трансляция ақпараты"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйді басқару элементтері"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профиль суретін таңдау"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Әдепкі пайдаланушы белгішесі"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Пернетақта"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 87c1aa2..275bcc1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"អាកាសធាតុ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"គុណភាព​ខ្យល់"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ព័ត៌មានអំពីការបញ្ជូន"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ការគ្រប់គ្រង​ផ្ទះ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ជ្រើសរើស​រូបភាព​កម្រង​ព័ត៌មាន"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ab437e8..7e9ff12 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ಹವಾಮಾನ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ವಾಯು ಗುಣಮಟ್ಟ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ಬಿತ್ತರಿಸಿದ ಮಾಹಿತಿ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ಹೋಮ್ ನಿಯಂತ್ರಣಗಳು"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ಪ್ರೊಫೈಲ್ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ಡೀಫಾಲ್ಟ್ ಬಳಕೆದಾರರ ಐಕಾನ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index 7138113..16b840b 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"시스템 설정 사용(기본)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"시스템 설정 사용(기본)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"시스템 설정 사용(기본)"</item>
     <item msgid="8003118270854840095">"44.1kHz"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 2b8ab75..fa96ad78 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -385,7 +385,7 @@
     <string name="force_msaa" msgid="4081288296137775550">"4x MSAA 강제 사용"</string>
     <string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 앱에서 4x MSAA 사용"</string>
     <string name="show_non_rect_clip" msgid="7499758654867881817">"사각형이 아닌 클립 작업 디버그"</string>
-    <string name="track_frame_time" msgid="522674651937771106">"프로필 HWUI 렌더링"</string>
+    <string name="track_frame_time" msgid="522674651937771106">"HWUI 렌더링 프로파일"</string>
     <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"GPU 디버그 레이어 사용 설정"</string>
     <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"디버그 앱에 GPU 디버그 레이어 로드 허용"</string>
     <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"상세 공급업체 로깅 사용 설정"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"날씨"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"대기 상태"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"전송 정보"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"홈 컨트롤"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"프로필 사진 선택하기"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"기본 사용자 아이콘"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"물리적 키보드"</string>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 40271f7..700aae1 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"карта13"</item>
     <item msgid="8147982633566548515">"карта14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e993e3f..9ebd47b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Аба ырайы"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Абанын сапаты"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Тышкы экранга чыгаруу маалыматы"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйдү көзөмөлдөө"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профилдин сүрөтүн тандоо"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Демейки колдонуучунун сүрөтчөсү"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Аппараттык баскычтоп"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index d295932..11d6d43 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ສະພາບອາກາດ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ຄຸນນະພາບ​ອາກາດ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ຂໍ້ມູນການສົ່ງສັນຍານ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ການຄວບຄຸມເຮືອນ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ເລືອກຮູບໂປຣໄຟລ໌"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml
index 946f69c..c0aafdc 100644
--- a/packages/SettingsLib/res/values-lt/arrays.xml
+++ b/packages/SettingsLib/res/values-lt/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Naudoti sistemos pasirink. (numatytasis)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Naudoti sistemos pasirink. (numatytasis)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Naudoti sistemos pasirink. (numatytasis)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index e38889b..e016956 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Oro kokybė"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Perdav. informacija"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Namų sist. valdikl."</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pasirinkite profilio nuotrauką"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Numatytojo naudotojo piktograma"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizinė klaviatūra"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index d899ada..ffd41635 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Laikapstākļi"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Gaisa kvalitāte"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Apraides informācija"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Mājas kontrolierīces"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profila attēla izvēle"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Noklusējuma lietotāja ikona"</string>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 9c46f21..41427c1 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Користи избор на системот (стандардно)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Користи избор на системот (стандардно)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Користи избор на системот (стандардно)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index c8033e3..26a87e3 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет на воздух"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Инфо за улогите"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за домот"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Изберете профилна слика"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Икона за стандарден корисник"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 4715e2a..98e3bd6 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 089f6af..5d9f799 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"വായു നിലവാരം"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"കാസ്റ്റ് വിവരങ്ങൾ"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ഹോം കൺട്രോളുകൾ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"പ്രൊഫൈൽ ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ഡിഫോൾട്ട് ഉപയോക്തൃ ഐക്കൺ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ഫിസിക്കൽ കീബോർഡ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/arrays.xml b/packages/SettingsLib/res/values-mn/arrays.xml
index 63fa53b..f3c10d7 100644
--- a/packages/SettingsLib/res/values-mn/arrays.xml
+++ b/packages/SettingsLib/res/values-mn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
     <item msgid="8003118270854840095">"44.1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index cc35053..df6fa89 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -447,8 +447,8 @@
     <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Дьютераномаль (улаан-ногоон)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгөний засвар нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Өнгөнүүдийг илүү нарийвчилж харах&lt;/li&gt; &lt;li&gt;&amp;nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах&lt;/li&gt; &lt;/ol&gt;"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулга"</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгө тохируулга нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Өнгөнүүдийг илүү нарийвчилж харах&lt;/li&gt; &lt;li&gt;&amp;nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Агаарын чанар"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Дамжуулах мэдээлэл"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Гэрийн удирдлага"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профайл зураг сонгох"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Өгөгдмөл хэрэглэгчийн дүрс тэмдэг"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Биет гар"</string>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index a54f990..c37baaa2 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टीम निवड वापरा (डीफॉल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 1907d00..4597549 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवेची गुणवत्ता"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसंबंधित माहिती"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो निवडा"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"डीफॉल्ट वापरकर्ता आयकन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"वास्तविक कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-ms/arrays.xml b/packages/SettingsLib/res/values-ms/arrays.xml
index b26ed23..b19f038 100644
--- a/packages/SettingsLib/res/values-ms/arrays.xml
+++ b/packages/SettingsLib/res/values-ms/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Lalai)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Lalai)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gunakan Pilihan Sistem (Lalai)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index b3929c9..1471040 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualiti Udara"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maklumat Pelakon"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kawalan Rumah"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pilih gambar profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna lalai"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Papan kekunci fizikal"</string>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index ed95dfe..3398c5b 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
     <item msgid="8003118270854840095">"၄၄.၁ kHz"</item>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f7a33a9..ee5601f 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"မိုးလေဝသ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"လေထုအရည်အသွေး"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ကာစ် အချက်အလက်"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"အိမ်သတ်မှတ်ချက်များ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ပရိုဖိုင်ပုံ ရွေးပါ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"မူရင်းအသုံးပြုသူ သင်္ကေတ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ပကတိ ကီးဘုတ်"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 317c2db..7e65fa0 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Bruk systemvalg (standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Bruk systemvalg (standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Bruk systemvalg (standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9b99631..a8fd256 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vær"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformasjon"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hjemkontroller"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Velg et profilbilde"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standard brukerikon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index b3c3ee2..ac1f187 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
     <item msgid="8003118270854840095">"४४.१ kHz"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1534cba..17b81b2 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"वायुको गुणस्तर"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसम्बन्धी जानकारी"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"घरायसी उपकरणका नियन्त्रण"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो छान्नुहोस्"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"प्रयोगकर्ताको डिफल्ट आइकन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"भौतिक किबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index e809452..7c90eab 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Systeemselectie gebruiken (standaard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gebruik systeemselectie (standaard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Systeemselectie gebruiken (standaard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index ba11b46..1251ed9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luchtkwaliteit"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformatie"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Bediening voor in huis"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Kies een profielfoto"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standaard gebruikersicoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiek toetsenbord"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 09fa59d..715f1eb 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -116,12 +116,12 @@
     <string name="bluetooth_profile_headset" msgid="5395952236133499331">"ଫୋନ୍‌ କଲ୍‌‌ଗୁଡ଼ିକ"</string>
     <string name="bluetooth_profile_opp" msgid="6692618568149493430">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌"</string>
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"ଇନ୍‌ପୁଟ୍‌ ଡିଭାଇସ୍"</string>
-    <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟର୍‌ନେଟ୍‌ ଆକ୍ସେସ୍"</string>
+    <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟରନେଟ ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ଇଣ୍ଟର୍‌ନେଟ୍‌ ସଂଯୋଗ ଶେୟାରିଙ୍ଗ"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"ଟେକ୍ସଟ୍ ମେସେଜ୍"</string>
-    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ୍‌"</string>
+    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
@@ -156,7 +156,7 @@
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ଦ୍ୱାରା ପେୟାରିଙ୍ଗ ପାଇଁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରିଦିଆଗଲା।"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"କମ୍ପ୍ୟୁଟର୍"</string>
     <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"ହେଡ୍‌ସେଟ୍‌"</string>
-    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ୍‌"</string>
+    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ"</string>
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ଇମେଜିଙ୍ଗ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ହେଡ୍‌ଫୋନ୍‌"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ଇନ୍‌ପୁଟ୍‌ ଉପକରଣ"</string>
@@ -246,7 +246,7 @@
     <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"ଛଅ ଡିଜିଟ୍ କୋଡ୍ ବ୍ୟବହାର କରି ନୂଆ ଡିଭାଇସଗୁଡ଼ିକୁ ପେୟାର୍ କରନ୍ତୁ"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"ପେୟାର୍ ହୋଇଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"ବର୍ତ୍ତମାନ ସଂଯୁକ୍ତ ଅଛି"</string>
-    <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସ୍ ବିବରଣୀ"</string>
+    <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସର ବିବରଣୀ"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"ଭୁଲିଯାଆନ୍ତୁ"</string>
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"ଡିଭାଇସ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟ: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"ସଂଯୋଗ ବିଫଳ ହେଲା"</string>
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ପାଣିପାଗ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ବାୟୁର ଗୁଣବତ୍ତା"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"କାଷ୍ଟ ସୂଚନା"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ହୋମ କଣ୍ଟ୍ରୋଲ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index a3fae22..0fd5c56 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9df0fef..51bf22d 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ਮੌਸਮ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ਹਵਾ ਦੀ ਕੁਆਲਿਟੀ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ਕਾਸਟ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ਹੋਮ ਕੰਟਰੋਲ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਤਸਵੀਰ ਚੁਣੋ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਰਤੋਂਕਾਰ ਪ੍ਰਤੀਕ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4599fd..64cbe78 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Pogoda"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Jakość powietrza"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Obsada"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Sterowanie domem"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Wybierz zdjęcie profilowe"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona domyślnego użytkownika"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/arrays.xml b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt-rBR/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -208,7 +208,7 @@
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
-    <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+    <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
   <string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade ­do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/arrays.xml b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
index 985bd51..e323455 100644
--- a/packages/SettingsLib/res/values-pt-rPT/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utilizar seleção do sistema (predefinido)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utilizar seleção do sistema (predefinido)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utilizar seleção do sistema (predefinido)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 4988136..ed3f642 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ctr. domésticos"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espaço inteligente"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolha uma imagem do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone do utilizador predefinido"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt/arrays.xml b/packages/SettingsLib/res/values-pt/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt/arrays.xml
+++ b/packages/SettingsLib/res/values-pt/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -208,7 +208,7 @@
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
-    <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+    <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
   <string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade ­do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index f1e9986..34b0ac9 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Folosiți selectarea sistemului (prestabilit)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Folosiți selectarea sistemului (prestabilit)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Folosiți selectarea sistemului (prestabilit)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f49fcdd..8f87a7c 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calitatea aerului"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informații artiști"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlul locuinței"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Alegeți o fotografie de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Pictograma prestabilită a utilizatorului"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastatură fizică"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 38930a5..e2f05dc 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество воздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Данные о трансляции"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизация дома"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Выберите фото профиля"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Значок пользователя по умолчанию"</string>
diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml
index 8386c1a..eaacfb8 100644
--- a/packages/SettingsLib/res/values-si/arrays.xml
+++ b/packages/SettingsLib/res/values-si/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index fa0296f..1d52e0b 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"කාලගුණය"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"වායු ගුණත්වය"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"විකාශ තතු"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"නිවෙස් පාලන"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ස්මාර්ට් අවකාශය"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"පැතිකඩ පින්තූරයක් තේරීම"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"පෙරනිමි පරිශීලක නිරූපකය"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"භෞතික යතුරු පුවරුව"</string>
diff --git a/packages/SettingsLib/res/values-sk/arrays.xml b/packages/SettingsLib/res/values-sk/arrays.xml
index 370b23f..bbfe969 100644
--- a/packages/SettingsLib/res/values-sk/arrays.xml
+++ b/packages/SettingsLib/res/values-sk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Použiť voľbu systému (predvolené)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Použiť voľbu systému (predvolené)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Použiť voľbu systému (predvolené)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a7c69c8..cf501cd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informácie o prenose"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládanie domácnosti"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Výber profilovej fotky"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Predvolená ikona používateľa"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fyzická klávesnica"</string>
diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml
index 6e33e38..b2003e5 100644
--- a/packages/SettingsLib/res/values-sl/arrays.xml
+++ b/packages/SettingsLib/res/values-sl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Uporabi sistemsko izbiro (privzeto)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Uporabi sistemsko izbiro (privzeto)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Uporabi sistemsko izbiro (privzeto)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 7334cea..bc89f5a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kakovost zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"O zasedbi"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Nadzor doma"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Hitri pregled"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Izbira profilne slike"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Privzeta ikona uporabnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizična tipkovnica"</string>
diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml
index 8a6d853..ed86380 100644
--- a/packages/SettingsLib/res/values-sq/arrays.xml
+++ b/packages/SettingsLib/res/values-sq/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 59359cdf..e7af25d 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Moti"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Cilësia e ajrit"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Të dhënat e aktorëve"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrollet e shtëpisë"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Zgjidh një fotografi profili"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona e parazgjedhur e përdoruesit"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fizike"</string>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index 7e198bf..a95e47b 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Користи избор система (подразумевано)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Користи избор система (подразумевано)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Користи избор система (подразумевано)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index eee3f89..a02d674 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Време"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет ваздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Подаци о пребацивању"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Управљање домом"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Одаберите слику профила"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Подразумевана икона корисника"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-sv/arrays.xml b/packages/SettingsLib/res/values-sv/arrays.xml
index f99a85b..c63465c 100644
--- a/packages/SettingsLib/res/values-sv/arrays.xml
+++ b/packages/SettingsLib/res/values-sv/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Använd systemval (standardinställning)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Använd systemval (standardinställning)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Använd systemval (standardinställning)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1a8815c..e135d1b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Väder"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info om rollistan"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hemstyrning"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Välj en profilbild"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon för standardanvändare"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiskt tangentbord"</string>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index dab4279..53dc6e5 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"ramani ya 13"</item>
     <item msgid="8147982633566548515">"ramani ya 14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
     <item msgid="8003118270854840095">"kHz 44.1"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 5f1d141..a900064 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hali ya Hewa"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ubora wa Hewa"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maelezo ya Wahusika"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Udhibiti wa Vifaa Nyumbani"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Chagua picha ya wasifu"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Aikoni chaguomsingi ya mtumiaji"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Kibodi halisi"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index a0f1fa6..957bd61 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index b8c37b0..645278c 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"வானிலை"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"காற்றின் தரம்"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"அலைபரப்புத் தகவல்"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ஹோம் கன்ட்ரோல்கள்"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"சுயவிவரப் படத்தைத் தேர்வுசெய்யுங்கள்"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"இயல்புநிலைப் பயனர் ஐகான்"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"கீபோர்டு"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 18a2758..d4361e5 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 504c827..de3f390 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"గాలి క్వాలిటీ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"కాస్ట్ సమాచారం"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"హోమ్ కంట్రోల్స్"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"స్మార్ట్‌స్పేస్"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ప్రొఫైల్ ఫోటోను ఎంచుకోండి"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ఆటోమేటిక్ సెట్టింగ్ యూజర్ చిహ్నం"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"భౌతిక కీబోర్డ్"</string>
diff --git a/packages/SettingsLib/res/values-th/arrays.xml b/packages/SettingsLib/res/values-th/arrays.xml
index 04a5f4d..782e95e 100644
--- a/packages/SettingsLib/res/values-th/arrays.xml
+++ b/packages/SettingsLib/res/values-th/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f33630d..dfb87a8 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"สภาพอากาศ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"คุณภาพอากาศ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ข้อมูลแคสต์"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ระบบควบคุมอุปกรณ์ในบ้าน"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"เลือกรูปโปรไฟล์"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ไอคอนผู้ใช้เริ่มต้น"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"แป้นพิมพ์จริง"</string>
diff --git a/packages/SettingsLib/res/values-tl/arrays.xml b/packages/SettingsLib/res/values-tl/arrays.xml
index 59cb1f3..19d3423 100644
--- a/packages/SettingsLib/res/values-tl/arrays.xml
+++ b/packages/SettingsLib/res/values-tl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gamitin ang Pagpili ng System (Default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gamitin ang Pagpili ng System (Default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gamitin ang Pagpili ng System (Default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f95f0e50..fdaece1 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Lagay ng Panahon"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kalidad ng Hangin"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Impormasyon ng Cast"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pumili ng larawan sa profile"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icon ng default na user"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Pisikal na keyboard"</string>
diff --git a/packages/SettingsLib/res/values-tr/arrays.xml b/packages/SettingsLib/res/values-tr/arrays.xml
index 5ed35fa..37891ae 100644
--- a/packages/SettingsLib/res/values-tr/arrays.xml
+++ b/packages/SettingsLib/res/values-tr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sistem Seçimini Kullan (Varsayılan)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sistem Seçimini Kullan (Varsayılan)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sistem Seçimini Kullan (Varsayılan)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 2fa2bd1..a2eb0e2 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hava durumu"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Hava Kalitesi"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayın Bilgisi"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ev Kontrolleri"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil fotoğrafı seçin"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Varsayılan kullanıcı simgesi"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziksel klavye"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 9be0855..63d7118 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якість повітря"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Акторський склад"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизація дому"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Виберіть зображення профілю"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Значок користувача за умовчанням"</string>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index d097458..db9941e 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 8eb9a11..2de47ec 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ہوا کا معیار"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"کاسٹ کرنے کی معلومات"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ہوم کنٹرولز"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"پروفائل کی تصویر منتخب کریں"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ڈیفالٹ صارف کا آئیکن"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"فزیکل کی بورڈ"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index a5c8683..707a54e 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ob-havo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havo sifati"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Translatsiya axboroti"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Uy boshqaruvi"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil rasmini tanlash"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Foydalanuvchining standart belgisi"</string>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index 31867e2..ee599d6 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 45a0465..82eb63d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Thời tiết"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Chất lượng không khí"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Thông tin về dàn nghệ sĩ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Điều khiển nhà"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Chọn một ảnh hồ sơ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Biểu tượng người dùng mặc định"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Bàn phím thực"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 973d7d0..2a85d31 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"使用系统选择(默认)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"使用系统选择(默认)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"使用系统选择(默认)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 2bc8a30..9f8007b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天气"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空气质量"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放信息"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"家居控制"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"选择个人资料照片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"默认用户图标"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/arrays.xml b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
index 87f3825..a84f0e2 100644
--- a/packages/SettingsLib/res/values-zh-rHK/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"使用系統選擇 (預設)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"使用系統選擇 (預設)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"使用系統選擇 (預設)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 114bc64..cc80ebc 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣質素"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放資料"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"智能家居"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人檔案相片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index 529287f..66aaa56b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"系統自動選擇 (預設)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"系統自動選擇 (預設)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"系統自動選擇 (預設)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index a7ae213..46aaef8 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣品質"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"演出者資訊"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"居家控制系統"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"智慧空間"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人資料相片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zu/arrays.xml b/packages/SettingsLib/res/values-zu/arrays.xml
index 59ead86..0494f1c 100644
--- a/packages/SettingsLib/res/values-zu/arrays.xml
+++ b/packages/SettingsLib/res/values-zu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"Imephu13"</item>
     <item msgid="8147982633566548515">"Imephu14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"I-AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+    <item msgid="3825367753087348007">"I-LDAC"</item>
+    <item msgid="328951785723550863">"I-LC3"</item>
+    <item msgid="506175145534048710">"I-Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"I-AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+    <item msgid="2553206901068987657">"I-LDAC"</item>
+    <item msgid="3940992993241040716">"I-LC3"</item>
+    <item msgid="7940970833006181407">"I-Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e037d12..e788357 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Isimo sezulu"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ikhwalithi Yomoya"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Ulwazi Lokusakaza"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Izilawuli Zasekhaya"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"I-Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Khetha isithombe sephrofayela"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Isithonjana somsebenzisi sokuzenzakalelayo"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Ikhibhodi ephathekayo"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5db2406..65696253 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -962,6 +962,9 @@
     <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
     <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
 
+    <!-- UI debug setting: enable desktop mode [CHAR LIMIT=25] -->
+    <string name="desktop_mode">Desktop mode</string>
+
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
     <!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index c7eb682..fb3f382 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -27,7 +27,6 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -76,14 +75,22 @@
     private static final String LIBRARY_TAIL_STRING = "</ul>\n<strong>Files</strong>";
 
     private static final String FILES_HEAD_STRING = "<ul class=\"files\">";
+    private static final String FILES_TAIL_STRING = "</ul>\n</div><!-- table of contents -->";
 
-    private static final String HTML_MIDDLE_STRING =
-            "</ul>\n"
-            + "</div><!-- table of contents -->\n"
-            + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+    private static final String CONTENT_HEAD_STRING =
+            "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+    private static final String CONTENT_TAIL_STRING = "</table>";
 
-    private static final String HTML_REAR_STRING =
-            "</table></body></html>";
+    private static final String IMAGES_HEAD_STRING =
+            "<div class=\"images-list\"><strong>Images</strong>\n<ul class=\"images\">";
+    private static final String IMAGES_TAIL_STRING = "</ul></div>\n";
+
+    private static final String PATH_COUNTS_HEAD_STRING =
+            "<div class=\"path-counts\"><table>\n  <tr><th>Path prefix</th><th>Count</th></tr>\n";
+    private static final String PATH_COUNTS_TAIL_STRING = "</table></div>\n";
+
+    private static final String HTML_TAIL_STRING =
+            "</body></html>";
 
     private final List<File> mXmlFiles;
 
@@ -137,13 +144,13 @@
         try {
             writer = new PrintWriter(outputFile);
 
-            generateHtml(mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap, writer,
-                noticeHeader);
+            generateHtml(mXmlFiles, mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap,
+                    writer, noticeHeader);
 
             writer.flush();
             writer.close();
             return true;
-        } catch (FileNotFoundException | SecurityException e) {
+        } catch (IOException | SecurityException e) {
             Log.e(TAG, "Failed to generate " + outputFile, e);
 
             if (writer != null) {
@@ -271,14 +278,33 @@
         return result.toString();
     }
 
+    private static String pathPrefix(String path) {
+        String prefix = path;
+        while (prefix.length() > 0 && prefix.substring(0, 1).equals("/")) {
+            prefix = prefix.substring(1);
+        }
+        int idx = prefix.indexOf("/");
+        if (idx > 0) {
+            prefix = prefix.substring(0, idx);
+        }
+        return prefix;
+    }
+
     @VisibleForTesting
-    static void generateHtml(Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
+    static void generateHtml(List<File> xmlFiles,
+            Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
             Map<String, String> contentIdToFileContentMap, PrintWriter writer,
-            String noticeHeader) {
+            String noticeHeader) throws IOException {
         List<String> fileNameList = new ArrayList();
         fileNameList.addAll(fileNameToLibraryToContentIdMap.keySet());
         Collections.sort(fileNameList);
 
+        SortedMap<String, Integer> prefixToCount = new TreeMap();
+        for (String f : fileNameList) {
+            String prefix = pathPrefix(f);
+            prefixToCount.merge(prefix, 1, Integer::sum);
+        }
+
         SortedMap<String, Set<String>> libraryToContentIdMap = new TreeMap();
         for (Map<String, Set<String>> libraryToContentValue :
                 fileNameToLibraryToContentIdMap.values()) {
@@ -324,70 +350,95 @@
             writer.println(LIBRARY_TAIL_STRING);
         }
 
-        writer.println(FILES_HEAD_STRING);
-
-        // Prints all the file list with a link to its license file content.
-        for (String fileName : fileNameList) {
-            for (Map.Entry<String, Set<String>> libToContentId :
-                    fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
-                String libraryName = libToContentId.getKey();
-                if (libraryName == null) {
-                    libraryName = "";
-                }
-                for (String contentId : libToContentId.getValue()) {
-                    // Assigns an id to a newly referred license file content.
-                    if (!contentIdToOrderMap.containsKey(contentId)) {
-                        contentIdToOrderMap.put(contentId, count);
-
-                        // An index in contentIdAndFileNamesList is the order of each element.
-                        contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
-                        count++;
+        if (!fileNameList.isEmpty()) {
+            writer.println(FILES_HEAD_STRING);
+            // Prints all the file list with a link to its license file content.
+            for (String fileName : fileNameList) {
+                for (Map.Entry<String, Set<String>> libToContentId :
+                        fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
+                    String libraryName = libToContentId.getKey();
+                    if (libraryName == null) {
+                        libraryName = "";
                     }
+                    for (String contentId : libToContentId.getValue()) {
+                        // Assigns an id to a newly referred license file content.
+                        if (!contentIdToOrderMap.containsKey(contentId)) {
+                            contentIdToOrderMap.put(contentId, count);
 
-                    int id = contentIdToOrderMap.get(contentId);
-                    ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
-                    List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
-                            libraryName, k -> new ArrayList());
-                    files.add(fileName);
+                            // An index in contentIdAndFileNamesList is the order of each element.
+                            contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+                            count++;
+                        }
+
+                        int id = contentIdToOrderMap.get(contentId);
+                        ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
+                        List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
+                                libraryName, k -> new ArrayList());
+                        files.add(fileName);
+                        if (TextUtils.isEmpty(libraryName)) {
+                            writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+                        } else {
+                            writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
+                                    id, fileName, libraryName);
+                        }
+                    }
+                }
+            }
+            writer.println(FILES_TAIL_STRING);
+        }
+
+        if (!contentIdAndFileNamesList.isEmpty()) {
+            writer.println(CONTENT_HEAD_STRING);
+            // Prints all contents of the license files in order of id.
+            for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
+                // Assigns an id to a newly referred license file content (should never happen here)
+                if (!contentIdToOrderMap.containsKey(contentIdAndFileNames.mContentId)) {
+                    contentIdToOrderMap.put(contentIdAndFileNames.mContentId, count);
+                    count++;
+                }
+                int id = contentIdToOrderMap.get(contentIdAndFileNames.mContentId);
+                writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", id);
+                for (Map.Entry<String, List<String>> libraryFiles :
+                        contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
+                    String libraryName = libraryFiles.getKey();
                     if (TextUtils.isEmpty(libraryName)) {
-                        writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+                        writer.println("<div class=\"label\">Notices for file(s):</div>");
                     } else {
-                        writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
-                                id, fileName, libraryName);
+                        writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
+                                libraryName);
                     }
+                    writer.println("<div class=\"file-list\">");
+                    for (String fileName : libraryFiles.getValue()) {
+                        writer.format("%s <br/>\n", fileName);
+                    }
+                    writer.println("</div><!-- file-list -->");
                 }
+                writer.println("<pre class=\"license-text\">");
+                writer.println(contentIdToFileContentMap.get(
+                        contentIdAndFileNames.mContentId));
+                writer.println("</pre><!-- license-text -->");
+                writer.println("</td></tr><!-- same-license -->");
             }
+            writer.println(CONTENT_TAIL_STRING);
         }
 
-        writer.println(HTML_MIDDLE_STRING);
-
-        count = 0;
-        // Prints all contents of the license files in order of id.
-        for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
-            writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
-            for (Map.Entry<String, List<String>> libraryFiles :
-                    contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
-                String libraryName = libraryFiles.getKey();
-                if (TextUtils.isEmpty(libraryName)) {
-                    writer.println("<div class=\"label\">Notices for file(s):</div>");
-                } else {
-                    writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
-                            libraryName);
-                }
-                writer.println("<div class=\"file-list\">");
-                for (String fileName : libraryFiles.getValue()) {
-                    writer.format("%s <br/>\n", fileName);
-                }
-                writer.println("</div><!-- file-list -->");
-                count++;
+        if (!xmlFiles.isEmpty()) {
+            writer.println(IMAGES_HEAD_STRING);
+            for (File file : xmlFiles) {
+                writer.format("  <li>%s</li>\n", pathPrefix(file.getCanonicalPath()));
             }
-            writer.println("<pre class=\"license-text\">");
-            writer.println(contentIdToFileContentMap.get(
-                    contentIdAndFileNames.mContentId));
-            writer.println("</pre><!-- license-text -->");
-            writer.println("</td></tr><!-- same-license -->");
+            writer.println(IMAGES_TAIL_STRING);
         }
 
-        writer.println(HTML_REAR_STRING);
+        if (!prefixToCount.isEmpty()) {
+            writer.println(PATH_COUNTS_HEAD_STRING);
+            for (Map.Entry<String, Integer> entry : prefixToCount.entrySet()) {
+                writer.format("  <tr><td>%s</td><td>%d</td></tr>\n",
+                        entry.getKey(), entry.getValue());
+            }
+            writer.println(PATH_COUNTS_TAIL_STRING);
+        }
+
+        writer.println(HTML_TAIL_STRING);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index d9262cc..766c036 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -528,7 +528,7 @@
     class RouterManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
-        public void onRoutesAdded(List<MediaRoute2Info> routes) {
+        public void onRoutesUpdated() {
             refreshDevices();
         }
 
@@ -540,16 +540,6 @@
         }
 
         @Override
-        public void onRoutesChanged(List<MediaRoute2Info> routes) {
-            refreshDevices();
-        }
-
-        @Override
-        public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-            refreshDevices();
-        }
-
-        @Override
         public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
             if (DEBUG) {
                 Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 09b0d7f..fe337d267 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -24,13 +24,16 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -116,6 +119,7 @@
             + "<li><a href=\"#id0\">/file0 - libA</a></li>\n"
             + "<li><a href=\"#id1\">/file0 - libB</a></li>\n"
             + "<li><a href=\"#id0\">/file1 - libA</a></li>\n"
+            + "<li><a href=\"#id0\">/file2 - libC</a></li>\n"
             + "</ul>\n"
             + "</div><!-- table of contents -->\n"
             + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
@@ -125,6 +129,10 @@
             + "/file0 <br/>\n"
             + "/file1 <br/>\n"
             + "</div><!-- file-list -->\n"
+            + "<div class=\"label\"><strong>libC</strong> used by:</div>\n"
+            + "<div class=\"file-list\">\n"
+            + "/file2 <br/>\n"
+            + "</div><!-- file-list -->\n"
             + "<pre class=\"license-text\">\n"
             + "license content #0\n"
             + "</pre><!-- license-text -->\n"
@@ -197,7 +205,8 @@
     }
 
     @Test
-    public void testGenerateHtml() {
+    public void testGenerateHtml() throws Exception {
+        List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
         Map<String, Set<String>> toBoth = new HashMap<>();
@@ -213,43 +222,48 @@
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
                 new PrintWriter(output), "");
         assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING);
     }
 
     @Test
-    public void testGenerateNewHtml() {
+    public void testGenerateNewHtml() throws Exception {
+        List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
         Map<String, Set<String>> toBoth = new HashMap<>();
         Map<String, Set<String>> toOne = new HashMap<>();
+        Map<String, Set<String>> toOther = new HashMap<>();
 
         toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
         toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
         toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+        toOther.put("libC", new HashSet<String>(Arrays.asList("0")));
 
         fileNameToLibraryToContentIdMap.put("/file0", toBoth);
         fileNameToLibraryToContentIdMap.put("/file1", toOne);
+        fileNameToLibraryToContentIdMap.put("/file2", toOther);
         contentIdToFileContentMap.put("0", "license content #0");
         contentIdToFileContentMap.put("1", "license content #1");
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
                 new PrintWriter(output), "");
         assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING);
     }
 
     @Test
-    public void testGenerateHtmlWithCustomHeading() {
+    public void testGenerateHtmlWithCustomHeading() throws Exception {
+        List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
         Map<String, Set<String>> toBoth = new HashMap<>();
         Map<String, Set<String>> toOne = new HashMap<>();
 
         toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
-        toOne.put("", new HashSet<String>(Arrays.asList("0")));
+        toOne.put("", new HashSet<String>(Arrays.asList("0", "1")));
 
         fileNameToLibraryToContentIdMap.put("/file0", toBoth);
         fileNameToLibraryToContentIdMap.put("/file1", toOne);
@@ -258,30 +272,34 @@
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
                 new PrintWriter(output), HTML_CUSTOM_HEADING);
         assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING);
     }
 
     @Test
-    public void testGenerateNewHtmlWithCustomHeading() {
+    public void testGenerateNewHtmlWithCustomHeading() throws Exception {
+        List<File> xmlFiles = new ArrayList<>();
         Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
         Map<String, String> contentIdToFileContentMap = new HashMap<>();
         Map<String, Set<String>> toBoth = new HashMap<>();
         Map<String, Set<String>> toOne = new HashMap<>();
+        Map<String, Set<String>> toOther = new HashMap<>();
 
         toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
         toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
         toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+        toOther.put("libC", new HashSet<String>(Arrays.asList("0")));
 
         fileNameToLibraryToContentIdMap.put("/file0", toBoth);
         fileNameToLibraryToContentIdMap.put("/file1", toOne);
+        fileNameToLibraryToContentIdMap.put("/file2", toOther);
         contentIdToFileContentMap.put("0", "license content #0");
         contentIdToFileContentMap.put("1", "license content #1");
 
         StringWriter output = new StringWriter();
         LicenseHtmlGeneratorFromXml.generateHtml(
-                fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+                xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
                 new PrintWriter(output), HTML_CUSTOM_HEADING);
         assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index ee7b7d6..f4af6e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -112,7 +112,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -135,7 +135,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -199,7 +199,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -222,7 +222,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -263,7 +263,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -286,7 +286,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 796b4c4..f0915f8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -337,8 +337,15 @@
         VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
-                Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT,
-                NON_NEGATIVE_INTEGER_VALIDATOR);
+                Global.Wearable.EARLY_UPDATES_STATUS,
+                new DiscreteValueValidator(
+                        new String[] {
+                                String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_NOT_STARTED),
+                                String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_STARTED),
+                                String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SUCCESS),
+                                String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED),
+                                String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED),
+                          }));
     }
 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 760f147..ba7a9bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,6 +98,7 @@
                     Settings.System.VOLUME_VOICE, // deprecated since API 2?
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+                    Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
@@ -656,7 +657,7 @@
                     Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
                     Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
                     Settings.Global.Wearable.BEDTIME_MODE,
-                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT);
+                    Settings.Global.Wearable.EARLY_UPDATES_STATUS);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 154a6fc..26feaf9 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -137,6 +137,22 @@
       ]
     }
   ],
+  "ironwood-postsubmit": [
+    {
+      "name": "PlatformScenarioTests",
+      "options": [
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "include-filter": "android.platform.test.scenario.sysui"
+        }
+      ]
+    }
+  ],
   "auto-end-to-end-postsubmit": [
     {
       "name": "AndroidAutomotiveHomeTests",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
new file mode 100644
index 0000000..925fae0e
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class BindServiceViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("bindService", "bindServiceAsUser", "unbindService")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "Binding or unbinding services are synchronous calls, please make " +
+                            "sure you're on a @Background Executor."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "BindServiceViaContextDetector",
+                briefDescription = "Service bound/unbound via Context, please make sure " +
+                        "you're on a background thread.",
+                explanation =
+                "Binding or unbinding services are synchronous calls to ActivityManager, " +
+                        "they usually take multiple milliseconds to complete and will make" +
+                        "the caller drop frames. Make sure you're on a @Background Executor.",
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
new file mode 100644
index 0000000..a629eee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "Please inject a @Main Executor instead."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+                Issue.create(
+                        id = "GetMainLooperViaContextDetector",
+                        briefDescription = "Please use idiomatic SystemUI executors, injecting " +
+                                "them via Dagger.",
+                        explanation = "Injecting the @Main Executor is preferred in order to make" +
+                                "dependencies explicit and increase testability. It's much " +
+                                "easier to pass a FakeExecutor on your test ctor than to " +
+                                "deal with loopers in unit tests.",
+                        category = Category.LINT,
+                        priority = 8,
+                        severity = Severity.WARNING,
+                        implementation = Implementation(GetMainLooperViaContextDetector::class.java,
+                                Scope.JAVA_FILE_SCOPE)
+                )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
new file mode 100644
index 0000000..b72d03d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("registerReceiver", "registerReceiverAsUser", "registerReceiverForAllUsers")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "BroadcastReceivers should be registered via BroadcastDispatcher."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                    id = "RegisterReceiverViaContextDetector",
+                    briefDescription = "Broadcast registrations via Context are blocking " +
+                            "calls. Please use BroadcastDispatcher.",
+                    explanation =
+                    "Context#registerReceiver is a blocking call to the system server, " +
+                            "making it very likely that you'll drop a frame. Please use " +
+                            "BroadcastDispatcher instead (or move this call to a " +
+                            "@Background Executor.)",
+                    category = Category.PERFORMANCE,
+                    priority = 8,
+                    severity = Severity.WARNING,
+                    implementation = Implementation(RegisterReceiverViaContextDetector::class.java,
+                            Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 397a110..78c6d72 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,7 +27,11 @@
 class SystemUIIssueRegistry : IssueRegistry() {
 
     override val issues: List<Issue>
-        get() = listOf(BroadcastSentViaContextDetector.ISSUE)
+        get() = listOf(BindServiceViaContextDetector.ISSUE,
+                BroadcastSentViaContextDetector.ISSUE,
+                GetMainLooperViaContextDetector.ISSUE,
+                RegisterReceiverViaContextDetector.ISSUE
+        )
 
     override val api: Int
         get() = CURRENT_API
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
new file mode 100644
index 0000000..bf685f7
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class BindServiceViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = BindServiceViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(
+            BindServiceViaContextDetector.ISSUE)
+
+    private val explanation = "Binding or unbinding services are synchronous calls"
+
+    @Test
+    fun testBindService() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass1 {
+                        public void bind(Context context) {
+                          Intent intent = new Intent(Intent.ACTION_VIEW);
+                          context.bindService(intent, null, 0);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testBindServiceAsUser() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context) {
+                          Intent intent = new Intent(Intent.ACTION_VIEW);
+                          context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testUnbindService() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.content.ServiceConnection;
+
+                    public class TestClass1 {
+                        public void unbind(Context context, ServiceConnection connection) {
+                          context.unbindService(connection);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.UserHandle;
+
+        public class Context {
+            public void bindService(Intent intent) {};
+            public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
+                                          UserHandle userHandle) {};
+            public void unbindService(ServiceConnection connection) {};
+        }
+        """
+    )
+
+    private val serviceConnectionStub: TestFile = java(
+            """
+        package android.content;
+
+        public class ServiceConnection {}
+        """
+    )
+
+    private val userHandleStub: TestFile = java(
+            """
+        package android.os;
+
+        public enum UserHandle {
+            ALL
+        }
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
new file mode 100644
index 0000000..ec761cd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = GetMainLooperViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
+
+    private val explanation = "Please inject a @Main Executor instead."
+
+    @Test
+    fun testGetMainThreadHandler() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.Handler;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Handler mainThreadHandler = context.getMainThreadHandler();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testGetMainLooper() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.Looper;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Looper mainLooper = context.getMainLooper();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testGetMainExecutor() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import java.util.concurrent.Executor;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Executor mainExecutor = context.getMainExecutor();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
+
+        public class Context {
+            public Looper getMainLooper() { return null; };
+            public Executor getMainExecutor() { return null; };
+            public Handler getMainThreadHandler() { return null; };
+        }
+        """
+    )
+
+    private val looperStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Looper {}
+        """
+    )
+
+    private val handlerStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Handler {}
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
new file mode 100644
index 0000000..76c0519
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(
+            RegisterReceiverViaContextDetector.ISSUE)
+
+    private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
+
+    @Test
+    fun testRegisterReceiver() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter) {
+                          context.registerReceiver(receiver, filter, 0);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testRegisterReceiverAsUser() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+                    import android.os.Handler;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter, Handler handler) {
+                          context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
+                            "permission", handler);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testRegisterReceiverForAllUsers() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+                    import android.os.Handler;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter, Handler handler) {
+                          context.registerReceiverForAllUsers(receiver, filter, "permission",
+                            handler);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.Handler;
+        import android.os.UserHandle;
+
+        public class Context {
+            public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                int flags) {};
+            public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+                IntentFilter filter, String broadcastPermission, Handler scheduler) {};
+            public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler) {};
+        }
+        """
+    )
+
+    private val broadcastReceiverStub: TestFile = java(
+            """
+        package android.content;
+
+        public class BroadcastReceiver {}
+        """
+    )
+
+    private val intentFilterStub: TestFile = java(
+            """
+        package android.content;
+
+        public class IntentFilter {}
+        """
+    )
+
+    private val handlerStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Handler {}
+        """
+    )
+
+    private val userHandleStub: TestFile = java(
+            """
+        package android.os;
+
+        public enum UserHandle {
+            ALL
+        }
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
+            userHandleStub)
+}
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index a96e5339..38d636d7 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -6,16 +6,16 @@
 
 ## Adding a new Quick Affordance
 ### Step 1: create a new quick affordance config
-* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
 * Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
   * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
   * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
   * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
-* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/domain/quickaffordance)
 
 ### Step 2: choose a position and priority
-* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceRegistry](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt)
 * Place the new class in one of the available positions in the `configsByPosition` property, note:
   * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
   * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
new file mode 100644
index 0000000..4da47af
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
@@ -0,0 +1,27 @@
+<?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.

+-->

+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

+    <item>

+        <shape android:shape="rectangle">

+            <solid android:color="@color/accessibility_magnifier_bg" />

+            <corners android:radius="24dp" />

+            <stroke

+                android:color="@color/accessibility_magnifier_bg_stroke"

+                android:width="1dp" />

+        </shape>

+    </item>

+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
new file mode 100644
index 0000000..5c9dd56
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+    Copyright (C) 2022 The Android Open Source Project

+

+    Licensed under the Apache License, Version 2.0 (the "License");

+    you may not use this file except in compliance with the License.

+    You may obtain a copy of the License at

+

+         http://www.apache.org/licenses/LICENSE-2.0

+

+    Unless required by applicable law or agreed to in writing, software

+    distributed under the License is distributed on an "AS IS" BASIS,

+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+    See the License for the specific language governing permissions and

+    limitations under the License.

+-->

+<ripple xmlns:android="http://schemas.android.com/apk/res/android"

+android:color="?android:attr/colorControlHighlight">

+<item android:id="@android:id/mask">

+    <shape android:shape="oval">

+        <solid android:color="@color/accessibility_magnifier_bg" />

+        <size

+            android:width="56dp"

+            android:height="56dp"/>

+        <corners android:radius="2dp"/>

+    </shape>

+</item>

+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
similarity index 64%
rename from libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
rename to packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
index 1938f45..f633b3e 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
@@ -1,21 +1,27 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2020 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <corners android:radius="@dimen/pip_menu_button_radius" />
-    <solid android:color="@color/tv_pip_menu_icon_bg" />
-</shape>
\ No newline at end of file
+<?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.

+-->

+<shape xmlns:android="http://schemas.android.com/apk/res/android"

+    android:shape="oval">

+    <solid android:color="@color/accessibility_magnifier_bg" />

+    <size

+        android:width="56dp"

+        android:height="56dp"/>

+    <corners android:radius="2dp"/>

+    <stroke

+        android:color="@color/accessibility_magnifier_bg_stroke"

+        android:width="1dp" />

+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
deleted file mode 100644
index 5084ca4..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
deleted file mode 100644
index c4f8181..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
deleted file mode 100644
index c05a8d5..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
deleted file mode 100644
index 1694429..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
new file mode 100644
index 0000000..a44a484
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
@@ -0,0 +1,29 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="32"
+    android:viewportHeight="32">
+  <path
+      android:pathData="M7.3334,24.6667L24.6674,7.3334M7.3334,7.3334L24.6674,24.6667"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
new file mode 100644
index 0000000..1ab0d4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
new file mode 100644
index 0000000..6fc89a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
new file mode 100644
index 0000000..fd73574
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 96db365..1bff559 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -23,18 +23,21 @@
             <size
                 android:height="@dimen/magnification_drag_view_size"
                 android:width="@dimen/magnification_drag_view_size"/>
+            <corners android:topLeftRadius="12dp"/>
+
         </shape>
     </item>
     <item
         android:gravity="center">
+
         <vector xmlns:android="http://schemas.android.com/apk/res/android"
-            android:width="30dp"
-            android:height="30dp"
             android:viewportWidth="24"
-            android:viewportHeight="24">
+            android:viewportHeight="24"
+            android:width="24dp"
+            android:height="24dp">
             <path
-                android:pathData="M18.19,12.44l-3.24,-1.62c1.29,-1 2.12,-2.56 2.12,-4.32c0,-3.03 -2.47,-5.5 -5.5,-5.5s-5.5,2.47 -5.5,5.5c0,2.13 1.22,3.98 3,4.89v3.26c-2.11,-0.45 -2.01,-0.44 -2.26,-0.44c-0.53,0 -1.03,0.21 -1.41,0.59L4,16.22l5.09,5.09C9.52,21.75 10.12,22 10.74,22h6.3c0.98,0 1.81,-0.7 1.97,-1.67l0.8,-4.71C20.03,14.32 19.38,13.04 18.19,12.44zM17.84,15.29L17.04,20h-6.3c-0.09,0 -0.17,-0.04 -0.24,-0.1l-3.68,-3.68l4.25,0.89V6.5c0,-0.28 0.22,-0.5 0.5,-0.5c0.28,0 0.5,0.22 0.5,0.5v6h1.76l3.46,1.73C17.69,14.43 17.91,14.86 17.84,15.29zM8.07,6.5c0,-1.93 1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5c0,0.95 -0.38,1.81 -1,2.44V6.5c0,-1.38 -1.12,-2.5 -2.5,-2.5c-1.38,0 -2.5,1.12 -2.5,2.5v2.44C8.45,8.31 8.07,7.45 8.07,6.5z"
-                android:fillColor="#FFFFFF"/>
+                android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z"
+                android:fillColor="#ffffff" />
         </vector>
     </item>
 </layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
index 36a1224..c7434f5 100644
--- a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
+++ b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
@@ -13,35 +13,19 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/magnification_switch_button_color" />
-            <size
-                android:width="48dp"
-                android:height="48dp" />
-        </shape>
-    </item>
-
-    <item
-        android:gravity="center">
-        <vector
-            android:width="36dp"
-            android:height="36dp"
-            android:viewportWidth="24"
-            android:viewportHeight="24">
-            <group>
-                <clip-path
-                    android:pathData="M0,0h24v24h-24z"/>
-                <path
-                    android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
-                    android:fillColor="#ffffff"/>
-                <path
-                    android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
-                    android:fillColor="#ffffff"/>
-            </group>
-        </vector>
-    </item>
-
-</layer-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="36dp"
+    android:height="36dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <group>
+        <clip-path
+            android:pathData="M0,0h24v24h-24z"/>
+        <path
+            android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
+            android:fillColor="#000000"/>
+        <path
+            android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
+            android:fillColor="#000000"/>
+    </group>
+</vector>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index e1b294f..d633803 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,11 +49,11 @@
 
     <FrameLayout
         android:id="@+id/biometric_icon_frame"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal">
 
-        <ImageView
+        <com.airbnb.lottie.LottieAnimationView
             android:id="@+id/biometric_icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index ce53e27..01ea31f 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -17,7 +17,7 @@
 <com.android.systemui.biometrics.AuthBiometricFingerprintView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contents"
-    android:layout_width="match_parent"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
new file mode 100644
index 0000000..6d8847c
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -0,0 +1,152 @@
+<?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/magnifier_panel_view"
+    android:layout_width="@dimen/magnification_max_size"
+    android:layout_height="match_parent"
+    android:background="@drawable/accessibility_magnification_setting_view_bg"
+    android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="@dimen/magnification_max_size"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/accessibility_magnifier_size"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:focusable="true"
+            android:layout_gravity="center_vertical|left"
+            android:layout_marginStart="20dp"/>
+
+        <Button
+            android:id="@+id/magnifier_edit_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/accessibility_magnifier_edit"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:focusable="true"
+            android:layout_gravity="right"
+            android:layout_marginEnd="20dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="@dimen/magnification_max_size"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <ImageButton
+            android:id="@+id/magnifier_small_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"
+            android:layout_marginStart="12dp"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_medium_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_large_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_full_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"
+            android:layout_marginEnd="12dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="8dp"
+        android:paddingEnd="20dp"
+        android:paddingStart="20dp"
+        android:focusable="true">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="?android:attr/selectableItemBackground"
+            android:ellipsize="marquee"
+            android:gravity="center_vertical"
+            android:minHeight="?android:attr/listPreferredItemHeightSmall"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:text="@string/accessibility_allow_diagonal_scrolling"
+                android:textAppearance="?android:attr/textAppearanceListItem"
+                android:textColor="?android:attr/textColorAlertDialogListItem" />
+        </LinearLayout>
+
+        <Switch
+            android:id="@+id/magnifier_horizontal_lock_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right|center"
+            android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+    </LinearLayout>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/accessibility_magnification_zoom"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:focusable="true"
+        android:layout_marginStart="20dp"
+        android:paddingTop="2dp"
+        android:paddingBottom="10dp"/>
+
+    <SeekBar
+        android:id="@+id/magnifier_zoom_seekbar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:progress="0"
+        android:max="6"
+        android:layout_marginEnd="20dp"
+        android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+
+
+    <Button
+        android:id="@+id/magnifier_close_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/accessibility_magnification_close"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:focusable="true"
+        android:layout_gravity="center_horizontal"
+        android:paddingBottom="24dp"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 7c755e5..0bff47c 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,33 +16,25 @@
   -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="wrap_content"
-             android:layout_height="wrap_content"
-             android:screenReaderFocusable="true">
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
     <View
+        android:id="@+id/magnification_inner_border"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/magnification_outer_border_margin"
-        android:importantForAccessibility="no"
-        android:background="@android:color/black"/>
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_margin="@dimen/magnification_inner_border_margin"
-        android:importantForAccessibility="no"
         android:background="@color/magnification_border_color"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="noHideDescendants">
+        android:layout_height="match_parent">
 
         <View
             android:id="@+id/left_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_above="@+id/bottom_handle"/>
+            android:layout_alignParentStart="true"/>
 
         <View
             android:id="@+id/top_handle"
@@ -54,7 +46,6 @@
             android:id="@+id/right_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_above="@+id/bottom_handle"
             android:layout_alignParentEnd="true"/>
 
         <View
@@ -68,7 +59,6 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
-
     </RelativeLayout>
 
     <ImageView
@@ -79,7 +69,17 @@
         android:layout_gravity="right|bottom"
         android:padding="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
-        android:importantForAccessibility="no"
         android:src="@drawable/ic_move_magnification"/>
 
-</FrameLayout>
\ No newline at end of file
+    <ImageView
+        android:id="@+id/close_button"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_margin="30dp"
+        android:padding="@dimen/magnification_switch_button_padding"
+        android:layout_gravity="right|bottom"
+        android:scaleType="center"
+        android:visibility="gone"
+        android:background="@drawable/accessibility_magnifier_btn_bg"
+        android:src="@drawable/ic_magnification_menu_close" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
new file mode 100644
index 0000000..cc68a83
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":25,"w":80,"h":80,"nm":"error_to_fingerprint","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.299],"y":[1]},"o":{"x":[0.543],"y":[0]},"t":5,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":25,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
new file mode 100644
index 0000000..aaf7e58
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":41,"w":80,"h":80,"nm":"error_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[1.4,1.4,0]},"t":10,"s":[50,50,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":30,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
new file mode 100644
index 0000000..78bccba
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"fingerprint_to_error","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.08],"y":[0,0.096]},"t":10,"s":[100,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.341,0.4],"y":[0,0]},"t":16,"s":[100,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.08,0.08],"y":[0.06,0.06]},"t":10,"s":[0,0]},{"i":{"x":[0.147,0.147],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":16,"s":[110,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":18,"s":[2.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":4,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
new file mode 100644
index 0000000..313c6c5
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":31,"w":80,"h":80,"nm":"fingerprint_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.107,46,0],"ix":2,"l":2},"a":{"a":0,"k":[2.75,2.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.43,0.43,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":7.199,"s":[141.866,141.866,100]},{"t":15,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.199,"s":[0]},{"t":8.400390625,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,2.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false}]},{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.233},"t":5.715,"s":[{"i":[[0,0],[-1.323,1.591],[-2.674,0],[-1.207,-1.781],[0,0]],"o":[[0,0],[1.298,-1.562],[2.657,0],[1.206,1.781],[0,0]],"v":[[-7.87,7.358],[-5.804,2.36],[0.009,-0.261],[5.845,2.706],[7.905,7.358]],"c":false}]},{"i":{"x":0.261,"y":1},"o":{"x":0.167,"y":0.233},"t":7.143,"s":[{"i":[[0,0],[-0.549,1.21],[-2.975,0],[-0.74,-2.398],[0,0]],"o":[[0,0],[0.796,-2.263],[2.964,0],[0.258,0.927],[0,0]],"v":[[-7.231,9.37],[-5.97,4.027],[0.012,0.056],[6.008,4.239],[7.277,9.37]],"c":false}]},{"i":{"x":0.23,"y":1},"o":{"x":0.123,"y":0},"t":12,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":26,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0,0],[-1.576,0],[0,-1.474],[0,0],[1.541,0.347],[0.142,0.379],[0,0],[0.383,0],[0,-0.549],[-0.256,-0.431],[-0.768,0.207],[0,0]],"o":[[-1.823,0.497],[0,-1.474],[1.576,0],[0,0],[0,0.549],[-0.378,-0.085],[0,0],[-0.142,-0.379],[-0.521,0],[-0.002,0.353],[0.171,0.288],[0.622,-0.344],[0,0]],"v":[[-0.41,3.841],[-2.717,1.917],[0.047,-0.756],[2.811,1.917],[2.811,1.996],[0.225,3.848],[0.995,2.366],[0.679,1.534],[-0.193,0.909],[-1.338,1.879],[-1.026,3.169],[0.445,3.702],[1.036,3.015]],"c":false}]},{"t":12.857421875,"s":[{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8.4,"s":[100]},{"t":11.3984375,"s":[0]}],"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false}]},{"i":{"x":0.331,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0.313,-0.134],[0.554,-0.317],[0.535,0],[0.203,0.046],[0.175,0.919],[0.232,0.216]],"o":[[-0.249,0.232],[-0.196,0.557],[-0.424,0.245],[-0.216,0],[-1.03,-0.044],[-0.288,-0.132],[0,0]],"v":[[11.468,-8.353],[10.62,-1.716],[9.232,-0.353],[7.057,0.034],[-7.634,-0.037],[-10.453,-1.739],[-11.238,-8.347]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":30,"cm":"1","dr":0},{"tm":51,"cm":"350ms\r","dr":0},{"tm":69,"cm":"650ms\r","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1eece4c..26bf103 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -166,8 +166,12 @@
 
     <!-- Window magnification colors -->
     <color name="magnification_border_color">#FF9900</color>
+    <color name="magnification_border_color_change">#0000FF</color>
     <color name="magnification_switch_button_color">#7F000000</color>
     <color name="magnification_drag_handle_color">#B3000000</color>
+    <color name="accessibility_magnifier_bg">#FCFCFC</color>
+    <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
+    <color name="accessibility_magnifier_icon_color">#252525</color>
 
     <!-- Volume dialog colors -->
     <color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 786b6b8..1168378 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -926,7 +926,8 @@
 
     <!-- Biometric Dialog values -->
     <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
-    <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
@@ -1065,8 +1066,10 @@
     <dimen name="magnification_frame_move_long">25dp</dimen>
     <dimen name="magnification_drag_view_size">36dp</dimen>
     <dimen name="magnification_controls_size">90dp</dimen>
-    <dimen name="magnification_switch_button_size">48dp</dimen>
+    <dimen name="magnification_switch_button_size">56dp</dimen>
+    <dimen name="magnification_switch_button_padding">6dp</dimen>
     <dimen name="magnification_switch_button_margin">16dp</dimen>
+    <dimen name="magnification_close_button_padding">15dp</dimen>
     <dimen name="magnifier_left_right_controls_width">35dp</dimen>
     <dimen name="magnifier_left_right_controls_height">45dp</dimen>
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
@@ -1074,10 +1077,15 @@
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
     <dimen name="magnification_max_frame_size">300dp</dimen>
+
     <!-- How far from the right edge of the screen you need to drag the window before the button
          repositions to the other side. -->
     <dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
 
+    <dimen name="magnification_drag_size">15dp</dimen>
+    <dimen name="magnification_max_size">360dp</dimen>
+    <dimen name="magnifier_panel_size">265dp</dimen>
+
     <!-- Home Controls -->
     <dimen name="controls_header_menu_size">48dp</dimen>
     <dimen name="controls_header_bottom_margin">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1bf3037..b844f11 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -799,7 +799,7 @@
     <!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to open</string>
 
-    <!-- Message shown when lock screen is unlocked (ie: by trust agent) and the user taps the empty space on the lock screen and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+    <!-- Message shown when lock screen is unlocked (ie: by trust agent or face auth). Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
     <string name="keyguard_unlock_press">Press the unlock icon to open</string>
 
     <!-- Message shown when non-bypass face authentication succeeds. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
@@ -813,6 +813,10 @@
     <!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
     <string name="keyguard_face_successful_unlock_press_alt_3">Face recognized. Press the unlock icon to open.</string>
 
+    <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+    <string name="keyguard_face_successful_unlock">Unlocked by face</string>
+    <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+    <string name="keyguard_face_successful_unlock_alt1">Face recognized</string>
 
     <!-- Messages shown when users press outside of udfps region during -->
     <string-array name="udfps_accessibility_touch_hints">
@@ -2105,6 +2109,41 @@
     <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
     <string name="magnification_mode_switch_click_label">Switch</string>
 
+    <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
+    <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
+    <!-- Title of the magnification option button Resize [CHAR LIMIT=NONE]-->
+    <string name="accessibility_resize">Resize</string>
+    <!-- Title of the magnification option button Change type [CHAR LIMIT=NONE]-->
+    <string name="accessibility_change_magnification_type">Change magnification type</string>
+    <!-- Title of the magnification option button End resizing [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_end_resizing">End resizing</string>
+
+    <!-- Description of the window magnification Top handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_top_handle">Top handle</string>
+    <!-- Description of the window magnification Left handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_left_handle">Left handle</string>
+    <!-- Description of the window magnification Right handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_right_handle">Right handle</string>
+    <!-- Description of the window magnification Bottom handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_bottom_handle">Bottom handle</string>
+
+    <!-- Title of the window magnification panel option Magnifier size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnifier_size">Magnifier size</string>
+    <!-- Title of the window magnification panel option Zoom [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_zoom">Zoom</string>
+    <!-- Click action label for magnification panel medium size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_medium">Medium</string>
+    <!-- Click action label for magnification panel small size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_small">Small</string>
+    <!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_large">Large</string>
+    <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_close">Close</string>
+    <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnifier_edit">Edit</string>
+    <!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
+
     <!-- Accessibility floating menu strings -->
     <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
     <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 203b236..7e42e1b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -170,7 +170,7 @@
     /** @return {@link SurfaceControl.Transaction} instance with vsync-id */
     public static SurfaceControl.Transaction newSurfaceControlTransaction() {
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+        tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
         return tx;
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8b5e3c1..3520274 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -54,6 +54,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -86,7 +87,6 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -714,7 +714,7 @@
     private void handleFingerprintAuthFailed() {
         Assert.isMainThread();
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
-            Log.d(TAG, "handleFingerprintAuthFailed()"
+            mLogger.d("handleFingerprintAuthFailed()"
                     + " triggered while waiting for cancellation, removing watchdog");
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
@@ -749,7 +749,7 @@
     private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
-            Log.d(TAG, "handleFingerprintAuthenticated()"
+            mLogger.d("handleFingerprintAuthenticated()"
                     + " triggered while waiting for cancellation, removing watchdog");
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
@@ -824,7 +824,7 @@
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
                 || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
-            Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
+            mLogger.logRetryAfterFpError(msgId, errString);
             mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
         }
 
@@ -2019,12 +2019,13 @@
         // in case authenticators aren't registered yet at this point:
         mAuthController.addCallback(new AuthController.Callback() {
             @Override
-            public void onAllAuthenticatorsRegistered() {
+            public void onAllAuthenticatorsRegistered(
+                    @BiometricAuthenticator.Modality int modality) {
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
             @Override
-            public void onEnrollmentsChanged() {
+            public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
             }
         });
@@ -3402,7 +3403,7 @@
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
                         info.visible));
             } catch (RemoteException e) {
-                Log.e(TAG, "unable to check task stack", e);
+                mLogger.logException(e, "unable to check task stack ");
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 99e0ce2..7a42803 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -271,7 +271,7 @@
      * like fingerprint authentication errors.
      *
      * @param message Message that indicates an error.
-     * @see KeyguardIndicationController.BaseKeyguardCallback#HIDE_DELAY_MS
+     * @see KeyguardIndicationController#DEFAULT_HIDE_DELAY_MS
      * @see KeyguardIndicationController#showTransientIndication(CharSequence)
      */
     public void onTrustAgentErrorMessage(CharSequence message) { }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 06e1828..d6974df 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 
 import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
@@ -29,6 +30,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedStateListDrawable;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Process;
 import android.os.VibrationAttributes;
@@ -701,13 +703,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsConfig();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsConfig();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsConfig();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsConfig();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 035b7f0..d718a24 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -198,6 +198,15 @@
                 { "Retrying face after HW unavailable, attempt $int1" })
     }
 
+    fun logRetryAfterFpError(msgId: Int, errString: String?) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = msgId
+            str1 = "$errString"
+        }, {
+            "Fingerprint retrying auth due to($int1) -> $str1"
+        })
+    }
+
     fun logRetryAfterFpHwUnavailable(retryCount: Int) {
         logBuffer.log(TAG, WARNING,
                 { int1 = retryCount },
@@ -270,12 +279,12 @@
                 { str1 = newTimeFormat },
                 { "handleTimeFormatUpdate timeFormat=$str1" })
     }
-
     fun logUdfpsPointerDown(sensorId: Int) {
         logBuffer.log(TAG, DEBUG,
                 { int1 = sensorId },
                 { "onUdfpsPointerDown, sensorId: $int1" })
     }
+
     fun logUdfpsPointerUp(sensorId: Int) {
         logBuffer.log(TAG, DEBUG,
                 { int1 = sensorId },
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 5c84ff3..614a87f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -87,7 +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.NotificationFilter;
 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;
@@ -102,7 +101,6 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -313,7 +311,6 @@
     @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
     @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
-    @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
     @Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
     @Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
     @Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@@ -322,7 +319,6 @@
     @Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
     @Inject Lazy<NotificationListener> mNotificationListener;
     @Inject Lazy<NotificationLogger> mNotificationLogger;
-    @Inject Lazy<NotificationFilter> mNotificationFilter;
     @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
     @Inject Lazy<SmartReplyController> mSmartReplyController;
     @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@@ -529,8 +525,6 @@
                 mNotificationLockscreenUserManager::get);
         mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
         mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
-        mProviders.put(NotificationGroupAlertTransferHelper.class,
-                mNotificationGroupAlertTransferHelper::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
         mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
         mProviders.put(NotificationRemoteInputManager.class,
@@ -538,7 +532,6 @@
         mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
         mProviders.put(NotificationListener.class, mNotificationListener::get);
         mProviders.put(NotificationLogger.class, mNotificationLogger::get);
-        mProviders.put(NotificationFilter.class, mNotificationFilter::get);
         mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
index 4b30ec3..c91082c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
@@ -23,6 +23,7 @@
 import android.os.Handler;
 import android.view.Display;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 /**
@@ -41,7 +42,7 @@
          *
          * @return {@code true} if this gesture is handled.
          */
-        boolean onSingleTap();
+        boolean onSingleTap(View view);
 
         /**
          * Called when the user is performing dragging gesture. It is started after the offset
@@ -52,7 +53,7 @@
          * @param offsetY The Y offset in screen coordinate.
          * @return {@code true} if this gesture is handled.
          */
-        boolean onDrag(float offsetX, float offsetY);
+        boolean onDrag(View view, float offsetX, float offsetY);
 
         /**
          * Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will
@@ -109,7 +110,7 @@
      * @param event The current motion event.
      * @return {@code True} if the {@link OnGestureListener} consumes the event, else false.
      */
-    boolean onTouch(MotionEvent event) {
+    boolean onTouch(View view, MotionEvent event) {
         final float rawX = event.getRawX();
         final float rawY = event.getRawY();
         boolean handled = false;
@@ -125,12 +126,12 @@
                 break;
             case MotionEvent.ACTION_MOVE:
                 stopSingleTapDetectionIfNeeded(rawX, rawY);
-                handled |= notifyDraggingGestureIfNeeded(rawX, rawY);
+                handled |= notifyDraggingGestureIfNeeded(view, rawX, rawY);
                 break;
             case MotionEvent.ACTION_UP:
                 stopSingleTapDetectionIfNeeded(rawX, rawY);
                 if (mDetectSingleTap) {
-                    handled |= mOnGestureListener.onSingleTap();
+                    handled |= mOnGestureListener.onSingleTap(view);
                 }
                 // Fall through
             case MotionEvent.ACTION_CANCEL:
@@ -163,7 +164,7 @@
         mDetectSingleTap = false;
     }
 
-    private boolean notifyDraggingGestureIfNeeded(float x, float y) {
+    private boolean notifyDraggingGestureIfNeeded(View view, float x, float y) {
         if (!mDraggingDetected) {
             return false;
         }
@@ -173,7 +174,7 @@
         final float offsetX = x - mPointerLocation.x;
         final float offsetY = y - mPointerLocation.y;
         mPointerLocation.set(x, y);
-        return mOnGestureListener.onDrag(offsetX, offsetY);
+        return mOnGestureListener.onDrag(view, offsetX, offsetY);
     }
 
     private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index dbd215d..59a5b15 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -213,18 +213,18 @@
         if (!mIsVisible) {
             return false;
         }
-        return mGestureDetector.onTouch(event);
+        return mGestureDetector.onTouch(v, event);
     }
 
     @Override
-    public boolean onSingleTap() {
+    public boolean onSingleTap(View v) {
         mSingleTapDetected = true;
         handleSingleTap();
         return true;
     }
 
     @Override
-    public boolean onDrag(float offsetX, float offsetY) {
+    public boolean onDrag(View v, float offsetX, float offsetY) {
         moveButton(offsetX, offsetY);
         return true;
     }
@@ -292,9 +292,12 @@
      * @param resetPosition if the button position needs be reset
      */
     private void showButton(int mode, boolean resetPosition) {
+        if (mode != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+            return;
+        }
         if (mMagnificationMode != mode) {
             mMagnificationMode = mode;
-            mImageView.setImageResource(getIconResId(mode));
+            mImageView.setImageResource(getIconResId(mMagnificationMode));
         }
         if (!mIsVisible) {
             onConfigurationChanged(mContext.getResources().getConfiguration());
@@ -408,6 +411,7 @@
 
     private static ImageView createView(Context context) {
         ImageView imageView = new ImageView(context);
+        imageView.setScaleType(ImageView.ScaleType.CENTER);
         imageView.setClickable(true);
         imageView.setFocusable(true);
         imageView.setAlpha(0f);
@@ -415,10 +419,8 @@
     }
 
     @VisibleForTesting
-    static int getIconResId(int mode) {
-        return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
-                ? R.drawable.ic_open_in_new_window
-                : R.drawable.ic_open_in_new_fullscreen;
+    static int getIconResId(int mode) { // TODO(b/242233514): delete non used param
+        return R.drawable.ic_open_in_new_window;
     }
 
     private static LayoutParams createLayoutParams(Context context) {
@@ -461,4 +463,4 @@
                             new Rect(0, 0, mImageView.getWidth(), mImageView.getHeight())));
         });
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d7fead1..f4701ed 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -233,6 +233,13 @@
     }
 
     @Override
+    public void onModeSwitch(int displayId, int newMode) {
+        if (mWindowMagnificationConnectionImpl != null) {
+            mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+        }
+    }
+
+    @Override
     public void requestWindowMagnificationConnection(boolean connect) {
         if (connect) {
             setWindowMagnificationConnection();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 1eedae6..813f4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -19,8 +19,11 @@
 import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowManager.LayoutParams;
 
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 
+import static java.lang.Math.abs;
+
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.NonNull;
@@ -41,6 +44,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
@@ -62,6 +67,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 import androidx.core.math.MathUtils;
 
@@ -93,6 +99,7 @@
     private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
+    private static final float[] MAGNIFICATION_SCALE_OPTIONS = {1.0f, 1.4f, 1.8f, 2.5f};
 
     private final Context mContext;
     private final Resources mResources;
@@ -145,7 +152,8 @@
     // The root of the mirrored content
     private SurfaceControl mMirrorSurface;
 
-    private View mDragView;
+    private ImageView mDragView;
+    private ImageView mCloseView;
     private View mLeftDrag;
     private View mTopDrag;
     private View mRightDrag;
@@ -162,6 +170,7 @@
     private final Runnable mWindowInsetChangeRunnable;
     // MirrorView is the mirror window which displays the magnified content.
     private View mMirrorView;
+    private View mMirrorBorderView;
     private SurfaceView mMirrorSurfaceView;
     private int mMirrorSurfaceMargin;
     private int mBorderDragSize;
@@ -172,6 +181,7 @@
      * repositions to the other side.
      */
     private int mButtonRepositionThresholdFromEdge;
+
     // The boundary of magnification frame.
     private final Rect mMagnificationFrameBoundary = new Rect();
     // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
@@ -192,6 +202,18 @@
     private boolean mOverlapWithGestureInsets;
     private boolean mIsDragging;
 
+    // Window Magnification Setting view
+    private WindowMagnificationSettings mWindowMagnificationSettings;
+
+    private static final int MAX_HORIZONTAL_MOVE_ANGLE = 50;
+    private static final int HORIZONTAL = 1;
+    private static final int VERTICAL = 0;
+    private static final double HORIZONTAL_LOCK_BASE =
+            Math.tan(Math.toRadians(MAX_HORIZONTAL_MOVE_ANGLE));
+
+    private boolean mAllowDiagonalScrolling = false;
+    private boolean mEditSizeEnable = false;
+
     @Nullable
     private MirrorWindowControl mMirrorWindowControl;
 
@@ -223,7 +245,12 @@
         mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
 
         mResources = mContext.getResources();
-        mScale = mResources.getInteger(R.integer.magnification_default_scale);
+        mScale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                mResources.getInteger(R.integer.magnification_default_scale),
+                UserHandle.USER_CURRENT);
+
+
         mBounceEffectDuration = mResources.getInteger(
                 com.android.internal.R.integer.config_shortAnimTime);
         updateDimensions();
@@ -241,6 +268,10 @@
         mGestureDetector =
                 new MagnificationGestureDetector(mContext, handler, this);
 
+        mWindowMagnificationSettings =
+                new WindowMagnificationSettings(mContext, mWindowMagnificationSettingsCallback,
+                        mSfVsyncFrameProvider);
+
         // Initialize listeners.
         mMirrorViewRunnable = () -> {
             if (mMirrorView != null) {
@@ -326,6 +357,26 @@
         return false;
     }
 
+    private void changeMagnificationSize(@MagnificationSize int index) {
+        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+        int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
+        setWindowSize(size, size);
+    }
+
+    private void setEditMagnifierSizeMode(boolean enable) {
+        mEditSizeEnable = enable;
+        applyResourcesValues();
+
+        if (isWindowVisible()) {
+            updateDimensions();
+            applyTapExcludeRegion();
+        }
+    }
+
+    private void setDiagonalScrolling(boolean enable) {
+        mAllowDiagonalScrolling = enable;
+    }
+
     /**
      * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
      * animation. If the window magnification is enabling, it runs the animation in reverse.
@@ -346,6 +397,9 @@
         if (!isWindowVisible()) {
             return;
         }
+
+        closeMagnificationSettings();
+
         if (mMirrorSurface != null) {
             mTransaction.remove(mMirrorSurface).apply();
             mMirrorSurface = null;
@@ -368,6 +422,8 @@
         mMirrorViewBounds.setEmpty();
         mSourceBounds.setEmpty();
         updateSystemUIStateIfNeeded();
+        setEditMagnifierSizeMode(false);
+
         mContext.unregisterComponentCallbacks(this);
         // Notify source bounds empty when magnification is deleted.
         mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect());
@@ -505,7 +561,7 @@
 
     /** Returns the rotation degree change of two {@link Surface.Rotation} */
     private int getDegreeFromRotation(@Surface.Rotation int newRotation,
-            @Surface.Rotation int oldRotation) {
+                                      @Surface.Rotation int oldRotation) {
         final int rotationDiff = oldRotation - newRotation;
         final int degree = (rotationDiff + 4) % 4 * 90;
         return degree;
@@ -534,6 +590,8 @@
         mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
         mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
 
+        mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
+
         // Allow taps to go through to the mirror SurfaceView below.
         mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
 
@@ -600,6 +658,18 @@
         }
     }
 
+    private void showMagnificationSettings() {
+        if (mWindowMagnificationSettings != null) {
+            mWindowMagnificationSettings.showSettingPanel();
+        }
+    }
+
+    private void closeMagnificationSettings() {
+        if (mWindowMagnificationSettings != null) {
+            mWindowMagnificationSettings.hideSettingPanel();
+        }
+    }
+
     /**
      * Sets the window size with given width and height in pixels without changing the
      * window center. The width or the height will be clamped in the range
@@ -668,12 +738,14 @@
         mTopDrag = mMirrorView.findViewById(R.id.top_handle);
         mRightDrag = mMirrorView.findViewById(R.id.right_handle);
         mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+        mCloseView = mMirrorView.findViewById(R.id.close_button);
 
         mDragView.setOnTouchListener(this);
         mLeftDrag.setOnTouchListener(this);
         mTopDrag.setOnTouchListener(this);
         mRightDrag.setOnTouchListener(this);
         mBottomDrag.setOnTouchListener(this);
+        mCloseView.setOnTouchListener(this);
     }
 
     /**
@@ -743,8 +815,8 @@
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
-                || v == mBottomDrag) {
-            return mGestureDetector.onTouch(event);
+                || v == mBottomDrag || v == mCloseView) {
+            return mGestureDetector.onTouch(v, event);
         }
         return false;
     }
@@ -768,6 +840,7 @@
         int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
         int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
         int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+
         mSourceBounds.set(left, top, right, bottom);
 
         // SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's
@@ -950,7 +1023,7 @@
      *                                       or {@link Float#NaN} to leave unchanged.
      */
     void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
-            float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
+                float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
         if (Float.compare(scale, 1.0f)  <= 0) {
             deleteWindowMagnification();
             return;
@@ -983,6 +1056,7 @@
         if (!isWindowVisible()) {
             createMirrorWindow();
             showControls();
+            applyResourcesValues();
         } else {
             modifyWindowMagnification(false);
         }
@@ -997,6 +1071,7 @@
         if (mAnimationController.isAnimating() || !isWindowVisible() || mScale == scale) {
             return;
         }
+
         enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
         mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
         mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
@@ -1014,19 +1089,42 @@
         if (mAnimationController.isAnimating() || mMirrorSurfaceView == null) {
             return;
         }
+
+        if (!mAllowDiagonalScrolling) {
+            int direction = selectDirectionForMove(abs(offsetX), abs(offsetY));
+
+            if (direction == HORIZONTAL) {
+                offsetY = 0;
+            } else {
+                offsetX = 0;
+            }
+        }
+
         if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) {
             modifyWindowMagnification(false);
         }
     }
 
     void moveWindowMagnifierToPosition(float positionX, float positionY,
-            IRemoteMagnificationAnimationCallback callback) {
+                                       IRemoteMagnificationAnimationCallback callback) {
         if (mMirrorSurfaceView == null) {
             return;
         }
         mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
     }
 
+    private int selectDirectionForMove(float diffX, float diffY) {
+        int direction = 0;
+        float result = diffY / diffX;
+
+        if (result <= HORIZONTAL_LOCK_BASE) {
+            direction = HORIZONTAL;  // horizontal move
+        } else {
+            direction = VERTICAL;  // vertical move
+        }
+        return direction;
+    }
+
     /**
      * Gets the scale.
      *
@@ -1072,17 +1170,143 @@
     }
 
     @Override
-    public boolean onSingleTap() {
-        animateBounceEffect();
+    public boolean onSingleTap(View view) {
+        handleSingleTap(view);
         return true;
     }
 
     @Override
-    public boolean onDrag(float offsetX, float offsetY) {
-        move((int) offsetX, (int) offsetY);
+    public boolean onDrag(View view, float offsetX, float offsetY) {
+        if (mEditSizeEnable) {
+            changeWindowSize(view, offsetX, offsetY);
+        } else {
+            move((int) offsetX, (int) offsetY);
+        }
         return true;
     }
 
+    private void handleSingleTap(View view) {
+        int id = view.getId();
+        if (id == R.id.drag_handle) {
+            showMagnificationSettings();
+        } else if (id == R.id.close_button) {
+            setEditMagnifierSizeMode(false);
+        } else {
+            animateBounceEffect();
+        }
+    }
+
+    private void applyResourcesValues() {
+        mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
+                ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+
+        if (mEditSizeEnable) {
+            mDragView.setVisibility(View.GONE);
+            mCloseView.setVisibility(View.VISIBLE);
+        } else {
+            mDragView.setVisibility(View.VISIBLE);
+            mCloseView.setVisibility(View.GONE);
+        }
+    }
+
+    public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+        boolean bRTL = isRTL(mContext);
+        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+
+        final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin;
+        final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin;
+
+        Rect tempRect = new Rect();
+        tempRect.set(mMagnificationFrame);
+
+        if (view == mLeftDrag) {
+            if (bRTL) {
+                tempRect.right += offsetX;
+                if (tempRect.right > mWindowBounds.width()) {
+                    return false;
+                }
+            } else {
+                tempRect.left += offsetX;
+                if (tempRect.left < 0) {
+                    return false;
+                }
+            }
+        } else if (view == mRightDrag) {
+            if (bRTL) {
+                tempRect.left += offsetX;
+                if (tempRect.left < 0) {
+                    return false;
+                }
+            } else {
+                tempRect.right += offsetX;
+                if (tempRect.right > mWindowBounds.width()) {
+                    return false;
+                }
+            }
+        } else if (view == mTopDrag) {
+            tempRect.top += offsetY;
+            if (tempRect.top < 0) {
+                return false;
+            }
+        } else if (view == mBottomDrag) {
+            tempRect.bottom += offsetY;
+            if (tempRect.bottom > mWindowBounds.height()) {
+                return false;
+            }
+        }
+
+        if (tempRect.width() < initSize || tempRect.height() < initSize
+                || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
+            return false;
+        }
+
+        mMagnificationFrame.set(tempRect);
+
+        computeBounceAnimationScale();
+        calculateMagnificationFrameBoundary();
+
+        modifyWindowMagnification(true);
+        return true;
+    }
+
+    private static boolean isRTL(Context context) {
+        Configuration config = context.getResources().getConfiguration();
+        if (config == null) {
+            return false;
+        }
+        return (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
+                == Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+    }
+
+    private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback =
+            new WindowMagnificationSettingsCallback() {
+        @Override
+        public void onSetDiagonalScrolling(boolean enable) {
+            setDiagonalScrolling(enable);
+        }
+
+        @Override
+        public void onModeSwitch(int newMode) {
+            mWindowMagnifierCallback.onModeSwitch(mDisplayId, newMode);
+        }
+
+        @Override
+        public void onSetMagnifierSize(@MagnificationSize int index) {
+            changeMagnificationSize(index);
+        }
+
+        @Override
+        public void onEditMagnifierSizeMode(boolean enable) {
+            setEditMagnifierSizeMode(enable);
+        }
+
+        @Override
+        public void onMagnifierScale(float scale) {
+            mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
+                    A11Y_ACTION_SCALE_RANGE.clamp(scale));
+        }
+    };
+
     @Override
     public boolean onStart(float x, float y) {
         mIsDragging = true;
@@ -1138,7 +1362,7 @@
         pw.println("      mMagnificationFrame:"
                 + (isWindowVisible() ? mMagnificationFrame : "empty"));
         pw.println("      mSourceBounds:"
-                 + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
+                + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
         pw.println("      mSystemGestureTop:" + mSystemGestureTop);
         pw.println("      mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX);
         pw.println("      mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY);
@@ -1149,6 +1373,11 @@
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
+            final AccessibilityAction clickAction = new AccessibilityAction(
+                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+                    R.string.magnification_mode_switch_click_label));
+            info.addAction(clickAction);
+            info.setClickable(true);
             info.addAction(
                     new AccessibilityAction(R.id.accessibility_action_zoom_in,
                             mContext.getString(R.string.accessibility_control_zoom_in)));
@@ -1176,7 +1405,10 @@
         }
 
         private boolean performA11yAction(int action) {
-            if (action == R.id.accessibility_action_zoom_in) {
+            if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+                // Simulate tapping the drag view so it opens the Settings.
+                handleSingleTap(mDragView);
+            } else if (action == R.id.accessibility_action_zoom_in) {
                 final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
                 mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
                         A11Y_ACTION_SCALE_RANGE.clamp(scale));
@@ -1199,4 +1431,4 @@
             return true;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
new file mode 100644
index 0000000..9cffd5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.IntDef;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Switch;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+
+/**
+ * Class to set value about WindowManificationSettings.
+ */
+class WindowMagnificationSettings implements MagnificationGestureDetector.OnGestureListener {
+    private static final String TAG = "WindowMagnificationSettings";
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
+    private final WindowManager mWindowManager;
+
+    private final Runnable mWindowInsetChangeRunnable;
+    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+
+    private final LayoutParams mParams;
+    @VisibleForTesting
+    final Rect mDraggableWindowBounds = new Rect();
+    private boolean mIsVisible = false;
+    private final MagnificationGestureDetector mGestureDetector;
+    private boolean mSingleTapDetected = false;
+
+    private SeekBar mZoomSeekbar;
+    private Switch mAllowDiagonalScrollingSwitch;
+    private LinearLayout mPanelView;
+    private LinearLayout mSettingView;
+    private LinearLayout mButtonView;
+    private ImageButton mSmallButton;
+    private ImageButton mMediumButton;
+    private ImageButton mLargeButton;
+    private Button mCloseButton;
+    private Button mEditButton;
+    private ImageButton mChangeModeButton;
+    private boolean mAllowDiagonalScrolling = false;
+    private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
+    private static final float A11Y_SCALE_MIN_VALUE = 2.0f;
+    private WindowMagnificationSettingsCallback mCallback;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            MagnificationSize.NONE,
+            MagnificationSize.SMALL,
+            MagnificationSize.MEDIUM,
+            MagnificationSize.LARGE,
+    })
+    /** Denotes the Magnification size type. */
+    @interface MagnificationSize {
+        int NONE = 0;
+        int SMALL  = 1;
+        int MEDIUM = 2;
+        int LARGE = 3;
+    }
+
+    @VisibleForTesting
+    WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
+                           SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
+        mContext = context;
+        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mSfVsyncFrameProvider = sfVsyncFrameProvider;
+        mCallback = callback;
+
+        mAllowDiagonalScrolling = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+                UserHandle.USER_CURRENT) == 1;
+
+        float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
+                UserHandle.USER_CURRENT);
+
+        mSettingView = (LinearLayout) View.inflate(context,
+                R.layout.window_magnification_settings_view, null);
+
+        mSettingView.setClickable(true);
+        mSettingView.setFocusable(true);
+        mSettingView.setOnTouchListener(this::onTouch);
+
+        mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view);
+        mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
+        mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
+        mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
+        mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+        mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
+        mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
+
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+        mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+        setSeekbarProgress(scale);
+        mAllowDiagonalScrollingSwitch =
+                (Switch) mSettingView.findViewById(R.id.magnifier_horizontal_lock_switch);
+        mAllowDiagonalScrollingSwitch.setChecked(mAllowDiagonalScrolling);
+        mAllowDiagonalScrollingSwitch.setOnCheckedChangeListener((view, checked) -> {
+            toggleDiagonalScrolling();
+        });
+
+        mParams = createLayoutParams(context);
+        applyResourcesValues();
+
+        mSmallButton.setAccessibilityDelegate(mButtonDelegate);
+        mSmallButton.setOnClickListener(mButtonClickListener);
+
+        mMediumButton.setAccessibilityDelegate(mButtonDelegate);
+        mMediumButton.setOnClickListener(mButtonClickListener);
+
+        mLargeButton.setAccessibilityDelegate(mButtonDelegate);
+        mLargeButton.setOnClickListener(mButtonClickListener);
+
+        mCloseButton.setAccessibilityDelegate(mButtonDelegate);
+        mCloseButton.setOnClickListener(mButtonClickListener);
+
+        mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
+        mChangeModeButton.setOnClickListener(mButtonClickListener);
+
+        mEditButton.setAccessibilityDelegate(mButtonDelegate);
+        mEditButton.setOnClickListener(mButtonClickListener);
+
+        mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+        mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
+            // Adds a pending post check to avoiding redundant calculation because this callback
+            // is sent frequently when the switch icon window dragged by the users.
+            if (!mSettingView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) {
+                mSettingView.getHandler().post(mWindowInsetChangeRunnable);
+            }
+            return v.onApplyWindowInsets(insets);
+        });
+
+        mGestureDetector = new MagnificationGestureDetector(context,
+                context.getMainThreadHandler(), this);
+    }
+
+    private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+            Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+                    UserHandle.USER_CURRENT);
+            mCallback.onMagnifierScale(scale);
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // Do nothing
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Do nothing
+        }
+    }
+
+    private CharSequence formatContentDescription(int viewId) {
+        if (viewId == R.id.magnifier_small_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_small);
+        } else if (viewId == R.id.magnifier_medium_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_medium);
+        } else if (viewId == R.id.magnifier_large_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_large);
+        } else if (viewId == R.id.magnifier_close_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_close);
+        } else if (viewId == R.id.magnifier_edit_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_resize);
+        } else {
+            return mContext.getResources().getString(
+                    R.string.magnification_mode_switch_description);
+        }
+    }
+
+    private void applyResourcesValues() {
+        final int padding = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_switch_button_padding);
+
+        PorterDuffColorFilter filter = new PorterDuffColorFilter(mContext.getColor(
+                R.color.accessibility_magnifier_icon_color), PorterDuff.Mode.SRC_ATOP);
+
+        mSmallButton.setImageResource(R.drawable.ic_magnification_menu_small);
+        mSmallButton.setColorFilter(filter);
+        mSmallButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mSmallButton.setPadding(padding, padding, padding, padding);
+
+        mMediumButton.setImageResource(R.drawable.ic_magnification_menu_medium);
+        mMediumButton.setColorFilter(filter);
+        mMediumButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mMediumButton.setPadding(padding, padding, padding, padding);
+
+        mLargeButton.setImageResource(R.drawable.ic_magnification_menu_large);
+        mLargeButton.setColorFilter(filter);
+        mLargeButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mLargeButton.setPadding(padding, padding, padding, padding);
+
+        mChangeModeButton.setImageResource(R.drawable.ic_open_in_new_fullscreen);
+        mChangeModeButton.setColorFilter(filter);
+        mChangeModeButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mChangeModeButton.setPadding(padding, padding, padding, padding);
+
+        mCloseButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mEditButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+    }
+
+    private final AccessibilityDelegate mButtonDelegate = new AccessibilityDelegate() {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+
+            info.setContentDescription(formatContentDescription(host.getId()));
+            final AccessibilityAction clickAction = new AccessibilityAction(
+                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+                    R.string.magnification_mode_switch_click_label));
+            info.addAction(clickAction);
+            info.setClickable(true);
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+                    mContext.getString(R.string.accessibility_control_move_up)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+                    mContext.getString(R.string.accessibility_control_move_down)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+                    mContext.getString(R.string.accessibility_control_move_left)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+                    mContext.getString(R.string.accessibility_control_move_right)));
+        }
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            if (performA11yAction(host, action)) {
+                return true;
+            }
+            return super.performAccessibilityAction(host, action, args);
+        }
+
+        private boolean performA11yAction(View view, int action) {
+            final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+            if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+                handleSingleTap(view);
+            } else if (action == R.id.accessibility_action_move_up) {
+                moveButton(0, -windowBounds.height());
+            } else if (action == R.id.accessibility_action_move_down) {
+                moveButton(0, windowBounds.height());
+            } else if (action == R.id.accessibility_action_move_left) {
+                moveButton(-windowBounds.width(), 0);
+            } else if (action == R.id.accessibility_action_move_right) {
+                moveButton(windowBounds.width(), 0);
+            } else {
+                return false;
+            }
+            return true;
+        }
+    };
+
+    private void applyResourcesValuesWithDensityChanged() {
+        if (mIsVisible) {
+            // Reset button to make its window layer always above the mirror window.
+            hideSettingPanel();
+            showSettingPanel(false);
+        }
+    }
+
+    private boolean onTouch(View v, MotionEvent event) {
+        if (!mIsVisible) {
+            return false;
+        }
+        return mGestureDetector.onTouch(v, event);
+    }
+
+    private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            int id = view.getId();
+            if (id == R.id.magnifier_small_button) {
+                setMagnifierSize(MagnificationSize.SMALL);
+            } else if (id == R.id.magnifier_medium_button) {
+                setMagnifierSize(MagnificationSize.MEDIUM);
+            } else if (id == R.id.magnifier_large_button) {
+                setMagnifierSize(MagnificationSize.LARGE);
+            } else if (id == R.id.magnifier_edit_button) {
+                editMagnifierSizeMode(true);
+            } else if (id == R.id.magnifier_close_button) {
+                hideSettingPanel();
+            } else if (id == R.id.magnifier_full_button) {
+                hideSettingPanel();
+                toggleMagnificationMode();
+            } else {
+                hideSettingPanel();
+            }
+        }
+    };
+
+    @Override
+    public boolean onSingleTap(View view) {
+        mSingleTapDetected = true;
+        handleSingleTap(view);
+        return true;
+    }
+
+    @Override
+    public boolean onDrag(View v, float offsetX, float offsetY) {
+        moveButton(offsetX, offsetY);
+        return true;
+    }
+
+    @Override
+    public boolean onStart(float x, float y) {
+        return true;
+    }
+
+    @Override
+    public boolean onFinish(float xOffset, float yOffset) {
+        if (!mSingleTapDetected) {
+            showSettingPanel();
+        }
+        mSingleTapDetected = false;
+        return true;
+    }
+
+    @VisibleForTesting
+    public ViewGroup getSettingView() {
+        return mSettingView;
+    }
+
+    private void moveButton(float offsetX, float offsetY) {
+        mSfVsyncFrameProvider.postFrameCallback(l -> {
+            mParams.x += offsetX;
+            mParams.y += offsetY;
+            updateButtonViewLayoutIfNeeded();
+        });
+    }
+
+    public void hideSettingPanel() {
+        if (!mIsVisible) {
+            return;
+        }
+
+        // Reset button status.
+        mWindowManager.removeView(mSettingView);
+        mIsVisible = false;
+        mParams.x = 0;
+        mParams.y = 0;
+
+        mContext.unregisterReceiver(mScreenOffReceiver);
+    }
+
+    public void showSettingPanel() {
+        showSettingPanel(true);
+    }
+
+    public void setScaleSeekbar(float scale) {
+        setSeekbarProgress(scale);
+    }
+
+    private void toggleMagnificationMode() {
+        mCallback.onModeSwitch(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    /**
+     * Shows magnification panel for set window magnification.
+     * When the panel is going to be visible by calling this method, the layout position can be
+     * reset depending on the flag.
+     *
+     * @param resetPosition if the button position needs be reset
+     */
+    private void showSettingPanel(boolean resetPosition) {
+        if (!mIsVisible) {
+            if (resetPosition) {
+                mDraggableWindowBounds.set(getDraggableWindowBounds());
+                mParams.x = mDraggableWindowBounds.right;
+                mParams.y = mDraggableWindowBounds.bottom;
+            }
+
+            mWindowManager.addView(mSettingView, mParams);
+
+            // Exclude magnification switch button from system gesture area.
+            setSystemGestureExclusion();
+            mIsVisible = true;
+        }
+        mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+    }
+
+    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            hideSettingPanel();
+        }
+    };
+
+    private void setSeekbarProgress(float scale) {
+        int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
+        if (index < 0) {
+            index = 0;
+        }
+        mZoomSeekbar.setProgress(index);
+    }
+
+    void onConfigurationChanged(int configDiff) {
+        if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+            applyResourcesValues();
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+            mDraggableWindowBounds.set(getDraggableWindowBounds());
+            // Keep the Y position with the same height ratio before the window bounds and
+            // draggable bounds are changed.
+            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
+                    / previousDraggableBounds.height();
+            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
+                    + mDraggableWindowBounds.top;
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+            applyResourcesValuesWithDensityChanged();
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+            updateAccessibilityWindowTitle();
+            return;
+        }
+    }
+
+    private void onWindowInsetChanged() {
+        final Rect newBounds = getDraggableWindowBounds();
+        if (mDraggableWindowBounds.equals(newBounds)) {
+            return;
+        }
+        mDraggableWindowBounds.set(newBounds);
+    }
+
+    private void updateButtonViewLayoutIfNeeded() {
+        if (mIsVisible) {
+            mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
+                    mDraggableWindowBounds.right);
+            mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top,
+                    mDraggableWindowBounds.bottom);
+            mWindowManager.updateViewLayout(mSettingView, mParams);
+        }
+    }
+
+    private void updateAccessibilityWindowTitle() {
+        mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
+        if (mIsVisible) {
+            mWindowManager.updateViewLayout(mSettingView, mParams);
+        }
+    }
+
+    private void handleSingleTap(View view) {
+        int id = view.getId();
+        if (id == R.id.magnifier_small_button) {
+            setMagnifierSize(MagnificationSize.SMALL);
+        } else if (id == R.id.magnifier_medium_button) {
+            setMagnifierSize(MagnificationSize.MEDIUM);
+        } else if (id == R.id.magnifier_large_button) {
+            setMagnifierSize(MagnificationSize.LARGE);
+        } else if (id == R.id.magnifier_full_button) {
+            hideSettingPanel();
+            toggleMagnificationMode();
+        } else {
+            hideSettingPanel();
+        }
+    }
+
+    public void editMagnifierSizeMode(boolean enable) {
+        setEditMagnifierSizeMode(enable);
+        hideSettingPanel();
+    }
+
+    private void setMagnifierSize(@MagnificationSize int index) {
+        mCallback.onSetMagnifierSize(index);
+    }
+
+    private void toggleDiagonalScrolling() {
+        boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+                UserHandle.USER_CURRENT) == 1;
+
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 0 : 1,
+                UserHandle.USER_CURRENT);
+
+        mCallback.onSetDiagonalScrolling(!enabled);
+    }
+
+    private void setEditMagnifierSizeMode(boolean enable) {
+        mCallback.onEditMagnifierSizeMode(enable);
+    }
+
+    private static LayoutParams createLayoutParams(Context context) {
+        final LayoutParams params = new LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+                LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSPARENT);
+        params.gravity = Gravity.TOP | Gravity.START;
+        params.accessibilityTitle = getAccessibilityWindowTitle(context);
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        return params;
+    }
+
+    private Rect getDraggableWindowBounds() {
+        final int layoutMargin = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_switch_button_margin);
+        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+        final Rect boundRect = new Rect(windowMetrics.getBounds());
+        boundRect.offsetTo(0, 0);
+        boundRect.inset(0, 0, mParams.width, mParams.height);
+        boundRect.inset(windowInsets);
+        boundRect.inset(layoutMargin, layoutMargin);
+
+        return boundRect;
+    }
+
+    private static String getAccessibilityWindowTitle(Context context) {
+        return context.getString(com.android.internal.R.string.android_system_label);
+    }
+
+    private void setSystemGestureExclusion() {
+        mSettingView.post(() -> {
+            mSettingView.setSystemGestureExclusionRects(
+                    Collections.singletonList(
+                            new Rect(0, 0, mSettingView.getWidth(), mSettingView.getHeight())));
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
new file mode 100644
index 0000000..22ec650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
+/**
+ * A callback to inform WindowMagnificationController about
+ * the setting value change or the user interaction.
+ */
+public interface WindowMagnificationSettingsCallback {
+
+    /**
+     * Called when change magnification size.
+     *
+     * @param index Magnification size index.
+     * 0 : MagnificationSize.NONE, 1 : MagnificationSize.SMALL,
+     * 2 : MagnificationSize.MEDIUM, 3: MagnificationSize.LARGE
+     */
+    void onSetMagnifierSize(@MagnificationSize int index);
+
+    /**
+     * Called when set allow diagonal scrolling.
+     *
+     * @param enable Allow diagonal scrolling enable value.
+     */
+    void onSetDiagonalScrolling(boolean enable);
+
+    /**
+     * Called when change magnification size on free mode.
+     *
+     * @param enable Free mode enable value.
+     */
+    void onEditMagnifierSizeMode(boolean enable);
+
+    /**
+     * Called when set magnification scale.
+     *
+     * @param scale Magnification scale value.
+     */
+    void onMagnifierScale(float scale);
+
+    /**
+     * Called when magnification mode changed.
+     *
+     * @param newMode Magnification mode
+     * 1 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 2 : ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+     */
+    void onModeSwitch(int newMode);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index c334ca6..19caaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -60,4 +60,12 @@
      * @param displayId The logical display id.
      */
     void onMove(int displayId);
+
+    /**
+     * Called when magnification mode changed.
+     *
+     * @param displayId The logical display id.
+     * @param newMode Magnification mode.
+     */
+    void onModeSwitch(int displayId, int newMode);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 55611f7..e60d4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -18,7 +18,7 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -33,8 +33,8 @@
 
 /** Face only icon animator for BiometricPrompt. */
 class AuthBiometricFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
     // false = dark to light, true = light to dark
@@ -76,44 +76,44 @@
         if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
             showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (newState == STATE_AUTHENTICATING) {
             startPulsing()
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_confirmed
+                    R.string.biometric_dialog_face_icon_description_confirmed
             )
         } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
             animateIconOnce(R.drawable.face_dialog_error_to_idle)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_PENDING_CONFIRMATION) {
             animateIconOnce(R.drawable.face_dialog_wink_from_dark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_IDLE) {
             showStaticDrawable(R.drawable.face_dialog_idle_static)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else {
             Log.w(TAG, "Unhandled state: $newState")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 3e4e573..40d1eff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -16,42 +16,43 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
 import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 
 /** Face/Fingerprint combined icon animator for BiometricPrompt. */
 class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthBiometricFingerprintIconController(context, iconView) {
 
     override val actsAsConfirmButton: Boolean = true
 
     override fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ): Boolean = when (newState) {
         STATE_PENDING_CONFIRMATION -> true
         STATE_AUTHENTICATED -> false
         else -> super.shouldAnimateForTransition(oldState, newState)
     }
 
+    @RawRes
     override fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? = when (newState) {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? = when (newState) {
         STATE_PENDING_CONFIRMATION -> {
             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
             } else {
-                context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
             }
         }
         STATE_AUTHENTICATED -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 606a73a..589ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -16,10 +16,9 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -32,42 +31,42 @@
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
-    var iconLayoutParamsSize = 0
+    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
                 return
             }
-            iconView.layoutParams.width = value
-            iconView.layoutParams.height = value
+            iconView.layoutParams.width = value.first
+            iconView.layoutParams.height = value.second
             field = value
         }
 
     init {
-        iconLayoutParamsSize = context.resources.getDimensionPixelSize(
-            R.dimen.biometric_dialog_fingerprint_icon_size
-        )
+        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
+                R.dimen.biometric_dialog_fingerprint_icon_width),
+                context.resources.getDimensionPixelSize(
+                        R.dimen.biometric_dialog_fingerprint_icon_height))
     }
 
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val icon = getAnimationForTransition(lastState, newState) ?: return
 
-        iconView.setImageDrawable(icon)
+        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
+            iconView.setAnimation(icon)
+        }
 
         val iconContentDescription = getIconContentDescription(newState)
         if (iconContentDescription != null) {
             iconView.contentDescription = iconContentDescription
         }
 
-        (icon as? AnimatedVectorDrawable)?.apply {
-            reset()
-            if (shouldAnimateForTransition(lastState, newState)) {
-                forceAnimationOnUI()
-                start()
-            }
+        iconView.frame = 0
+        if (shouldAnimateForTransition(lastState, newState)) {
+            iconView.playAnimation()
         }
     }
 
@@ -86,8 +85,8 @@
     }
 
     protected open fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
@@ -97,24 +96,27 @@
         else -> false
     }
 
+    @RawRes
     protected open fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? {
         val id = when (newState) {
             STATE_HELP,
-            STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_ERROR -> {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
             STATE_AUTHENTICATING_ANIMATING_IN,
             STATE_AUTHENTICATING -> {
                 if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.drawable.fingerprint_dialog_error_to_fp
+                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
                 } else {
-                    R.drawable.fingerprint_dialog_fp_to_error
+                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
                 }
             }
-            STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
             else -> return null
         }
-        return if (id != null) context.getDrawable(id) else null
+        return if (id != null) return id else null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 24046f0..31baa0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -90,8 +90,9 @@
 
     fun updateOverrideIconLayoutParamsSize() {
         udfpsAdapter?.let {
-            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamsSize =
-                    it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
+                    Pair(sensorDiameter, sensorDiameter)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index ce5e600..15f487b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -22,15 +22,15 @@
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 
 private const val TAG = "AuthIconController"
 
 /** Controller for animating the BiometricPrompt icon/affordance. */
 abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: ImageView
+        protected val context: Context,
+        protected val iconView: LottieAnimationView
 ) : Animatable2.AnimationCallback() {
 
     /** If this controller should ignore events and pause. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d7ae9ef..e866b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -41,14 +41,14 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.util.LargeScreenUtils;
+
+import com.airbnb.lottie.LottieAnimationView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -133,7 +133,7 @@
     private TextView mSubtitleView;
     private TextView mDescriptionView;
     private View mIconHolderView;
-    protected ImageView mIconView;
+    protected LottieAnimationView mIconView;
     protected TextView mIndicatorView;
 
     @VisibleForTesting @NonNull AuthIconController mIconController;
@@ -824,25 +824,12 @@
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
 
-    private boolean isLargeDisplay() {
-        return LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        final boolean isLargeDisplay = isLargeDisplay();
-
-        final int newWidth;
-        if (isLargeDisplay) {
-            // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
-            //  want to consider moving this to an overlay.
-            newWidth = 2 * Math.min(width, height) / 3;
-        } else {
-            newWidth = Math.min(width, height);
-        }
+        final int newWidth = Math.min(width, height);
 
         // Use "newWidth" instead, so the landscape dialog width is the same as the portrait
         // width.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 47ff59c..282f251 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -46,6 +46,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -156,25 +157,6 @@
         }
     };
 
-    private final IFingerprintAuthenticatorsRegisteredCallback
-            mFingerprintAuthenticatorsRegisteredCallback =
-            new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
-                @Override
-                public void onAllAuthenticatorsRegistered(
-                        List<FingerprintSensorPropertiesInternal> sensors) {
-                    mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors));
-                }
-            };
-
-    private final BiometricStateListener mBiometricStateListener =
-            new BiometricStateListener() {
-                @Override
-                public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
-                    mHandler.post(
-                            () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments));
-                }
-            };
-
     @VisibleForTesting
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -249,8 +231,8 @@
             List<FingerprintSensorPropertiesInternal> sensors) {
         mExecution.assertIsMainThread();
         if (DEBUG) {
-            Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString(
-                    sensors.toArray()));
+            Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: "
+                    + Arrays.toString(sensors.toArray()));
         }
         mAllFingerprintAuthenticatorsRegistered = true;
         mFpProps = sensors;
@@ -292,15 +274,42 @@
             mSidefpsController = mSidefpsControllerFactory.get();
         }
 
-        mFingerprintManager.registerBiometricStateListener(mBiometricStateListener);
+        mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
+            @Override
+            public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+                mHandler.post(() -> handleEnrollmentsChanged(
+                        TYPE_FINGERPRINT, userId, sensorId, hasEnrollments));
+            }
+        });
         updateFingerprintLocation();
 
         for (Callback cb : mCallbacks) {
-            cb.onAllAuthenticatorsRegistered();
+            cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
         }
     }
 
-    private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+    private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+        mExecution.assertIsMainThread();
+        if (DEBUG) {
+            Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString(
+                    sensors.toArray()));
+        }
+
+        mFaceManager.registerBiometricStateListener(new BiometricStateListener() {
+            @Override
+            public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+                mHandler.post(() -> handleEnrollmentsChanged(
+                        TYPE_FACE, userId, sensorId, hasEnrollments));
+            }
+        });
+
+        for (Callback cb : mCallbacks) {
+            cb.onAllAuthenticatorsRegistered(TYPE_FACE);
+        }
+    }
+
+    private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId,
+            boolean hasEnrollments) {
         mExecution.assertIsMainThread();
         Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
                 + ", hasEnrollments: " + hasEnrollments);
@@ -314,7 +323,7 @@
             }
         }
         for (Callback cb : mCallbacks) {
-            cb.onEnrollmentsChanged();
+            cb.onEnrollmentsChanged(modality);
         }
     }
 
@@ -700,7 +709,26 @@
 
         if (mFingerprintManager != null) {
             mFingerprintManager.addAuthenticatorsRegisteredCallback(
-                    mFingerprintAuthenticatorsRegisteredCallback);
+                    new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FingerprintSensorPropertiesInternal> sensors) {
+                            mHandler.post(() ->
+                                    handleAllFingerprintAuthenticatorsRegistered(sensors));
+                        }
+                    });
+        }
+        if (mFaceManager != null) {
+            mFaceManager.addAuthenticatorsRegisteredCallback(
+                    new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FaceSensorPropertiesInternal> sensors) {
+                            mHandler.post(() ->
+                                    handleAllFaceAuthenticatorsRegistered(sensors));
+                        }
+                    }
+            );
         }
 
         mStableDisplaySize = mDisplayManager.getStableDisplaySize();
@@ -1116,13 +1144,13 @@
          * Called when authenticators are registered. If authenticators are already
          * registered before this call, this callback will never be triggered.
          */
-        default void onAllAuthenticatorsRegistered() {}
+        default void onAllAuthenticatorsRegistered(@Modality int modality) {}
 
         /**
-         * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+         * Called when enrollments have changed. This is called after boot and on changes to
          * enrollment.
          */
-        default void onEnrollmentsChanged() {}
+        default void onEnrollmentsChanged(@Modality int modality) {}
 
         /**
          * Called when the biometric prompt starts showing.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 38fab8f..fd3f600 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -308,7 +308,7 @@
 
     private val authControllerCallback =
         object : AuthController.Callback {
-            override fun onAllAuthenticatorsRegistered() {
+            override fun onAllAuthenticatorsRegistered(modality: Int) {
                 updateUdfpsDependentParams()
                 updateSensorLocation()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
index 6f3beac..a0b19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -35,7 +35,7 @@
      *             " - downstream canceled or failed.",
      *          it,
      *    )
-     *}
+     * }
      * ```
      */
     fun <T> SendChannel<T>.trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
deleted file mode 100644
index f697c0a..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
+++ /dev/null
@@ -1,34 +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.common.domain.model
-
-import com.android.systemui.common.data.model.Position as DataLayerPosition
-
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-) {
-    companion object {
-        fun DataLayerPosition.toDomainLayer(): Position {
-            return Position(
-                x = x,
-                y = y,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
rename to packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
index 7c9df10..52f6167 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.systemui.common.shared.model
 
 /** Models a two-dimensional position */
 data class Position(
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index a9e310d..7da2cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
 
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.Handler;
 import android.util.Log;
 import android.view.Display;
@@ -232,13 +235,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsController();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsController();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsController();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsController();
+            }
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163..997a6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
@@ -29,6 +31,7 @@
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.net.Uri;
 import android.os.Handler;
@@ -835,13 +838,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsEnrolled();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsEnrolled();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsEnrolled();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsEnrolled();
+            }
         }
 
         private void updateUdfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index bd00ce6..1f52fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -27,6 +27,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,8 +57,11 @@
 public class KeyguardIndicationRotateTextViewController extends
         ViewController<KeyguardIndicationTextView> implements Dumpable {
     public static String TAG = "KgIndicationRotatingCtrl";
-    private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
-    public static final long IMPORTANT_MSG_MIN_DURATION = 2000L + 600L; // 2000ms + [Y in duration]
+    private static final long DEFAULT_INDICATION_SHOW_LENGTH =
+            KeyguardIndicationController.DEFAULT_HIDE_DELAY_MS
+                    - KeyguardIndicationTextView.Y_IN_DURATION;
+    public static final long IMPORTANT_MSG_MIN_DURATION =
+            2000L + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final StatusBarStateController mStatusBarStateController;
     private final float mMaxAlpha;
@@ -375,6 +379,7 @@
     public static final int INDICATION_TYPE_USER_LOCKED = 8;
     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
     public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
+    public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12;
 
     @IntDef({
             INDICATION_TYPE_NONE,
@@ -388,7 +393,8 @@
             INDICATION_TYPE_RESTING,
             INDICATION_TYPE_USER_LOCKED,
             INDICATION_TYPE_REVERSE_CHARGING,
-            INDICATION_TYPE_BIOMETRIC_MESSAGE
+            INDICATION_TYPE_BIOMETRIC_MESSAGE,
+            INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface IndicationType{}
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 4ff008f..56f1ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,7 +43,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
-import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -70,8 +70,8 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             FalsingModule.class,
+            KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
-            KeyguardUseCaseModule.class,
         })
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 43c4fa0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,65 +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.keyguard.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
-interface KeyguardQuickAffordanceRepository {
-    fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
-}
-
-/** Real implementation of [KeyguardQuickAffordanceRepository] */
-@SysUISingleton
-class KeyguardQuickAffordanceRepositoryImpl
-@Inject
-constructor(
-    private val configs: KeyguardQuickAffordanceConfigs,
-) : KeyguardQuickAffordanceRepository {
-
-    /** Returns an observable for the quick affordance model in the given position. */
-    override fun affordance(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        val configs = configs.getAll(position)
-        return combine(configs.map { config -> config.state }) { states ->
-            val index = states.indexOfFirst { state -> state is State.Visible }
-            val visibleState =
-                if (index != -1) {
-                    states[index] as State.Visible
-                } else {
-                    null
-                }
-            if (visibleState != null) {
-                KeyguardQuickAffordanceModel.Visible(
-                    configKey = configs[index]::class,
-                    icon = visibleState.icon,
-                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
-                )
-            } else {
-                KeyguardQuickAffordanceModel.Hidden
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 62cf1a6..e52d9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 1a5670c..d15d7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -16,22 +16,10 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
 interface KeyguardRepositoryModule {
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
-
-    @Binds
-    fun keyguardQuickAffordanceRepository(
-        impl: KeyguardQuickAffordanceRepositoryImpl
-    ): KeyguardQuickAffordanceRepository
-
-    @Binds
-    fun keyguardQuickAffordanceConfigs(
-        impl: KeyguardQuickAffordanceConfigsImpl
-    ): KeyguardQuickAffordanceConfigs
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
new file mode 100644
index 0000000..ede50b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic specifically related to the keyguard bottom area. */
+@SysUISingleton
+class KeyguardBottomAreaInteractor
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    /** Whether to animate the next doze mode transition. */
+    val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+    /** The amount of alpha for the UI components of the bottom area. */
+    val alpha: Flow<Float> = repository.bottomAreaAlpha
+    /** The position of the keyguard clock. */
+    val clockPosition: Flow<Position> = repository.clockPosition
+
+    fun setClockPosition(x: Int, y: Int) {
+        repository.setClockPosition(x, y)
+    }
+
+    fun setAlpha(alpha: Float) {
+        repository.setBottomAreaAlpha(alpha)
+    }
+
+    fun setAnimateDozingTransitions(animate: Boolean) {
+        repository.setAnimateDozingTransitions(animate)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
new file mode 100644
index 0000000..dccc941
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
+ */
+@SysUISingleton
+class KeyguardInteractor
+@Inject
+constructor(
+    repository: KeyguardRepository,
+) {
+    /**
+     * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+     * all.
+     */
+    val dozeAmount: Flow<Float> = repository.dozeAmount
+    /** Whether the system is in doze mode. */
+    val isDozing: Flow<Boolean> = repository.isDozing
+    /** Whether the keyguard is showing ot not. */
+    val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
new file mode 100644
index 0000000..9a69e26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyguardQuickAffordanceInteractor
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+) {
+    /** Returns an observable for the quick affordance at the given position. */
+    fun quickAffordance(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        return combine(
+            quickAffordanceInternal(position),
+            keyguardInteractor.isDozing,
+            keyguardInteractor.isKeyguardShowing,
+        ) { affordance, isDozing, isKeyguardShowing ->
+            if (!isDozing && isKeyguardShowing) {
+                affordance
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+
+    /**
+     * Notifies that a quick affordance has been clicked by the user.
+     *
+     * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
+     * the affordance that was clicked
+     * @param animationController An optional controller for the activity-launch animation
+     */
+    fun onQuickAffordanceClicked(
+        configKey: KClass<out KeyguardQuickAffordanceConfig>,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
+        when (val result = config.onQuickAffordanceClicked(animationController)) {
+            is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+                launchQuickAffordance(
+                    intent = result.intent,
+                    canShowWhileLocked = result.canShowWhileLocked,
+                    animationController = animationController
+                )
+            is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+        }
+    }
+
+    private fun quickAffordanceInternal(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        val configs = registry.getAll(position)
+        return combine(configs.map { config -> config.state }) { states ->
+            val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+            if (index != -1) {
+                val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+                KeyguardQuickAffordanceModel.Visible(
+                    configKey = configs[index]::class,
+                    icon = visibleState.icon,
+                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+                )
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+
+    private fun launchQuickAffordance(
+        intent: Intent,
+        canShowWhileLocked: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+        val strongAuthFlags =
+            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+        val needsToUnlockFirst =
+            when {
+                strongAuthFlags ==
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+                !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+                else -> false
+            }
+        if (needsToUnlockFirst) {
+            activityStarter.postStartActivityDismissingKeyguard(
+                intent,
+                0 /* delay */,
+                animationController
+            )
+        } else {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 09785df..eff1469 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -15,11 +15,11 @@
  *
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
 
 import androidx.annotation.StringRes
 import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
 /**
@@ -27,7 +27,6 @@
  * lock-screen).
  */
 sealed class KeyguardQuickAffordanceModel {
-
     /** No affordance should show up. */
     object Hidden : KeyguardQuickAffordanceModel()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
index b71e15d..581dafa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
 
 /** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
 enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index df44957..8f32ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Context
 import android.content.Intent
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 67a776e..8fb952c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
 import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
new file mode 100644
index 0000000..94024d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardQuickAffordanceModule {
+    @Binds
+    fun keyguardQuickAffordanceRegistry(
+        impl: KeyguardQuickAffordanceRegistryImpl
+    ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 7164215..ad40ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -15,29 +15,25 @@
  *
  */
 
-package com.android.systemui.keyguard.data.config
+package com.android.systemui.keyguard.domain.quickaffordance
 
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import javax.inject.Inject
 import kotlin.reflect.KClass
 
-/** Injectable provider of the positioning of the known quick affordance configs. */
-interface KeyguardQuickAffordanceConfigs {
-    fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
-    fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+/** Central registry of all known quick affordance configs. */
+interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
+    fun getAll(position: KeyguardQuickAffordancePosition): List<T>
+    fun get(configClass: KClass<out T>): T
 }
 
-class KeyguardQuickAffordanceConfigsImpl
+class KeyguardQuickAffordanceRegistryImpl
 @Inject
 constructor(
     homeControls: HomeControlsKeyguardQuickAffordanceConfig,
     quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
     qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceConfigs {
+) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
     private val configsByPosition =
         mapOf(
             KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index ea6497e..c8e5e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index cc5a997..885af33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
deleted file mode 100644
index c44c2c9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ /dev/null
@@ -1,29 +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.keyguard.domain.usecase
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardUseCaseModule {
-
-    @Binds
-    fun launchQuickAffordance(
-        impl: LaunchKeyguardQuickAffordanceUseCaseImpl
-    ): LaunchKeyguardQuickAffordanceUseCase
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 3d60399..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
-
-/** Defines interface for classes that can launch a quick affordance. */
-interface LaunchKeyguardQuickAffordanceUseCase {
-    operator fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
-    )
-}
-
-/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
-class LaunchKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
-    private val lockPatternUtils: LockPatternUtils,
-    private val keyguardStateController: KeyguardStateController,
-    private val userTracker: UserTracker,
-    private val activityStarter: ActivityStarter,
-) : LaunchKeyguardQuickAffordanceUseCase {
-    override operator fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
-    ) {
-        @StrongAuthFlags
-        val strongAuthFlags =
-            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
-        val needsToUnlockFirst =
-            when {
-                strongAuthFlags ==
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
-                !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
-                else -> false
-            }
-        if (needsToUnlockFirst) {
-            activityStarter.postStartActivityDismissingKeyguard(
-                intent,
-                0 /* delay */,
-                animationController
-            )
-        } else {
-            activityStarter.startActivity(
-                intent,
-                true /* dismissShade */,
-                animationController,
-                true /* showOverLockscreenWhenLocked */,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
deleted file mode 100644
index ca37727..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
+++ /dev/null
@@ -1,32 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether doze state transitions should animate the bottom area */
-class ObserveAnimateBottomAreaTransitionsUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.animateBottomAreaDozingTransitions
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 151b704..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,32 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the alpha of the bottom area */
-class ObserveBottomAreaAlphaUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Float> {
-        return repository.bottomAreaAlpha
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
deleted file mode 100644
index 02c5737..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.common.domain.model.Position
-import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Use-case for observing the position of the clock. */
-class ObserveClockPositionUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Position> {
-        return repository.clockPosition.map { it.toDomainLayer() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
deleted file mode 100644
index 56d6182..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
+++ /dev/null
@@ -1,32 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the amount of doze the system is in. */
-class ObserveDozeAmountUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Float> {
-        return repository.dozeAmount
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
deleted file mode 100644
index 1d241d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
+++ /dev/null
@@ -1,32 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether we are dozing. */
-class ObserveIsDozingUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.isDozing
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
deleted file mode 100644
index 11af123..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
+++ /dev/null
@@ -1,39 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Use-case for observing whether the keyguard is currently being shown.
- *
- * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
- * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
- * invisible to reveal the "occluding activity").
- */
-class ObserveIsKeyguardShowingUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.isKeyguardShowing
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index eef8ec3..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,49 +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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Use-case for observing the model of a quick affordance in the keyguard. */
-class ObserveKeyguardQuickAffordanceUseCase
-@Inject
-constructor(
-    private val repository: KeyguardQuickAffordanceRepository,
-    private val isDozingUseCase: ObserveIsDozingUseCase,
-    private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) {
-    operator fun invoke(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return combine(
-            repository.affordance(position),
-            isDozingUseCase(),
-            isKeyguardShowingUseCase(),
-        ) { affordance, isDozing, isKeyguardShowing ->
-            if (!isDozing && isKeyguardShowing) {
-                affordance
-            } else {
-                KeyguardQuickAffordanceModel.Hidden
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
deleted file mode 100644
index f8db90f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
+++ /dev/null
@@ -1,49 +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.keyguard.domain.usecase
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import javax.inject.Inject
-import kotlin.reflect.KClass
-
-/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
-class OnKeyguardQuickAffordanceClickedUseCase
-@Inject
-constructor(
-    private val configs: KeyguardQuickAffordanceConfigs,
-    private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
-) {
-    operator fun invoke(
-        configKey: KClass<*>,
-        animationController: ActivityLaunchAnimator.Controller?,
-    ) {
-        @Suppress("UNCHECKED_CAST")
-        val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
-        when (val result = config.onQuickAffordanceClicked(animationController)) {
-            is OnClickedResult.StartActivity ->
-                launchAffordanceUseCase(
-                    intent = result.intent,
-                    canShowWhileLocked = result.canShowWhileLocked,
-                    animationController = animationController
-                )
-            is OnClickedResult.Handled -> Unit
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
deleted file mode 100644
index 8f746e5..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the updated clock position. */
-class SetClockPositionUseCase
-@Inject
-constructor(
-    private val keyguardRepository: KeyguardRepository,
-) {
-    operator fun invoke(x: Int, y: Int) {
-        keyguardRepository.setClockPosition(x, y)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 90be1ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the alpha that the keyguard bottom area should use */
-class SetKeyguardBottomAreaAlphaUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(alpha: Float) {
-        repository.setBottomAreaAlpha(alpha)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
deleted file mode 100644
index 007780a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/**
- * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
- */
-class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(animate: Boolean) {
-        repository.setAnimateDozingTransitions(animate)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 04d30bf..19c6249 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -95,35 +95,23 @@
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    combine(viewModel.startButton, viewModel.animateButtonReveal) {
-                            buttonModel,
-                            animateReveal ->
-                            Pair(buttonModel, animateReveal)
-                        }
-                        .collect { (buttonModel, animateReveal) ->
-                            updateButton(
-                                view = startButton,
-                                viewModel = buttonModel,
-                                animateReveal = animateReveal,
-                                falsingManager = falsingManager,
-                            )
-                        }
+                    viewModel.startButton.collect { buttonModel ->
+                        updateButton(
+                            view = startButton,
+                            viewModel = buttonModel,
+                            falsingManager = falsingManager,
+                        )
+                    }
                 }
 
                 launch {
-                    combine(viewModel.endButton, viewModel.animateButtonReveal) {
-                            buttonModel,
-                            animateReveal ->
-                            Pair(buttonModel, animateReveal)
-                        }
-                        .collect { (buttonModel, animateReveal) ->
-                            updateButton(
-                                view = endButton,
-                                viewModel = buttonModel,
-                                animateReveal = animateReveal,
-                                falsingManager = falsingManager,
-                            )
-                        }
+                    viewModel.endButton.collect { buttonModel ->
+                        updateButton(
+                            view = endButton,
+                            viewModel = buttonModel,
+                            falsingManager = falsingManager,
+                        )
+                    }
                 }
 
                 launch {
@@ -226,7 +214,6 @@
     private fun updateButton(
         view: ImageView,
         viewModel: KeyguardQuickAffordanceViewModel,
-        animateReveal: Boolean,
         falsingManager: FalsingManager,
     ) {
         if (!viewModel.isVisible) {
@@ -236,7 +223,7 @@
 
         if (!view.isVisible) {
             view.isVisible = true
-            if (animateReveal) {
+            if (viewModel.animateReveal) {
                 view.alpha = 0f
                 view.translationY = view.height / 2f
                 view
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 4b69a81..01d5e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -17,15 +17,11 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -36,13 +32,9 @@
 class KeyguardBottomAreaViewModel
 @Inject
 constructor(
-    private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
-    private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
-    observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
-    observeIsDozingUseCase: ObserveIsDozingUseCase,
-    observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
-    private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
-    observeClockPositionUseCase: ObserveClockPositionUseCase,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     private val burnInHelperWrapper: BurnInHelperWrapper,
 ) {
     /** An observable for the view-model of the "start button" quick affordance. */
@@ -51,17 +43,11 @@
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
         button(KeyguardQuickAffordancePosition.BOTTOM_END)
-    /**
-     * An observable for whether the next time a quick action button becomes visible, it should
-     * animate.
-     */
-    val animateButtonReveal: Flow<Boolean> =
-        observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
     /** An observable for whether the overlay container should be visible. */
     val isOverlayContainerVisible: Flow<Boolean> =
-        observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+        keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
     /** An observable for the alpha level for the entire bottom area. */
-    val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+    val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
     /** An observable for whether the indication area should be padded. */
     val isIndicationAreaPadded: Flow<Boolean> =
         combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -70,11 +56,11 @@
             .distinctUntilChanged()
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+        bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return observeDozeAmountUseCase()
+        return keyguardInteractor.dozeAmount
             .map { dozeAmount ->
                 dozeAmount *
                     (burnInHelperWrapper.burnInOffset(
@@ -88,21 +74,28 @@
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
-        return observeQuickAffordanceUseCase(position)
-            .map { model -> model.toViewModel() }
+        return combine(
+                quickAffordanceInteractor.quickAffordance(position),
+                bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+            ) { model, animateReveal ->
+                model.toViewModel(animateReveal)
+            }
             .distinctUntilChanged()
     }
 
-    private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+    private fun KeyguardQuickAffordanceModel.toViewModel(
+        animateReveal: Boolean,
+    ): KeyguardQuickAffordanceViewModel {
         return when (this) {
             is KeyguardQuickAffordanceModel.Visible ->
                 KeyguardQuickAffordanceViewModel(
                     configKey = configKey,
                     isVisible = true,
+                    animateReveal = animateReveal,
                     icon = icon,
                     contentDescriptionResourceId = contentDescriptionResourceId,
                     onClicked = { parameters ->
-                        onQuickAffordanceClickedUseCase(
+                        quickAffordanceInteractor.onQuickAffordanceClicked(
                             configKey = parameters.configKey,
                             animationController = parameters.animationController,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 2417998..985ab62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -19,18 +19,21 @@
 import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
 /** Models the UI state of a keyguard quick affordance button. */
 data class KeyguardQuickAffordanceViewModel(
-    val configKey: KClass<*>? = null,
+    val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
     val isVisible: Boolean = false,
+    /** Whether to animate the transition of the quick affordance from invisible to visible. */
+    val animateReveal: Boolean = false,
     val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
     @StringRes val contentDescriptionResourceId: Int = 0,
     val onClicked: (OnClickedParameters) -> Unit = {},
 ) {
     data class OnClickedParameters(
-        val configKey: KClass<*>,
+        val configKey: KClass<out KeyguardQuickAffordanceConfig>,
         val animationController: ActivityLaunchAnimator.Controller?,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index d701f33..c790cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs
 
 import android.content.Intent
+import android.content.res.Configuration
 import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings
@@ -38,9 +39,11 @@
 import com.android.systemui.qs.dagger.QSScope
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -69,18 +72,43 @@
     private val uiEventLogger: UiEventLogger,
     @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
     private val globalSetting: GlobalSettings,
-    private val handler: Handler
+    private val handler: Handler,
+    private val configurationController: ConfigurationController,
 ) : ViewController<FooterActionsView>(view) {
 
     private var globalActionsDialog: GlobalActionsDialogLite? = null
 
     private var lastExpansion = -1f
     private var listening: Boolean = false
+    private var inSplitShade = false
 
-    private val alphaAnimator = TouchAnimator.Builder()
-            .addFloat(mView, "alpha", 0f, 1f)
-            .setStartDelay(0.9f)
+    private val singleShadeAnimator by lazy {
+        // In single shade, the actions footer should only appear at the end of the expansion,
+        // so that it doesn't overlap with the notifications panel.
+        TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
+    }
+
+    private val splitShadeAnimator by lazy {
+        // The Actions footer view has its own background which is the same color as the qs panel's
+        // background.
+        // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
+        // more opaque than the rest of the panel's background. Only applies to split shade.
+        val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
+        val bgAlphaAnimator =
+            TouchAnimator.Builder()
+                .addFloat(mView, "backgroundAlpha", 0f, 1f)
+                .setStartDelay(0.9f)
+                .build()
+        // In split shade, we want the actions footer to fade in exactly at the same time as the
+        // rest of the shade, as there is no overlap.
+        TouchAnimator.Builder()
+            .addFloat(alphaAnimator, "position", 0f, 1f)
+            .addFloat(bgAlphaAnimator, "position", 0f, 1f)
             .build()
+    }
+
+    private val animators: TouchAnimator
+        get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
 
     var visible = true
         set(value) {
@@ -95,9 +123,7 @@
     private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
 
     @VisibleForTesting
-    internal val securityFootersSeparator = View(context).apply {
-        visibility = View.GONE
-    }
+    internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
 
     private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
         val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -133,6 +159,17 @@
         }
     }
 
+    private val configurationListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onConfigChanged(newConfig: Configuration?) {
+                updateResources()
+            }
+        }
+
+    private fun updateResources() {
+        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
+    }
+
     override fun onInit() {
         multiUserSwitchController.init()
         securityFooterController.init()
@@ -189,6 +226,9 @@
         securityFooterController.setOnVisibilityChangedListener(visibilityListener)
         fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
 
+        configurationController.addCallback(configurationListener)
+
+        updateResources()
         updateView()
     }
 
@@ -201,6 +241,7 @@
         globalActionsDialog = null
         setListening(false)
         multiUserSetting.isListening = false
+        configurationController.removeCallback(configurationListener)
     }
 
     fun setListening(listening: Boolean) {
@@ -224,7 +265,7 @@
     }
 
     fun setExpansion(headerExpansionFraction: Float) {
-        alphaAnimator.setPosition(headerExpansionFraction)
+        animators.setPosition(headerExpansionFraction)
     }
 
     fun setKeyguardShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 05038b7..309ac2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -27,6 +27,7 @@
 import android.view.View
 import android.widget.ImageView
 import android.widget.LinearLayout
+import androidx.annotation.Keep
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
 import com.android.systemui.R
@@ -45,6 +46,19 @@
     private var qsDisabled = false
     private var expansionAmount = 0f
 
+    /**
+     * Sets the alpha of the background of this view.
+     *
+     * Used from a [TouchAnimator] in the controller.
+     */
+    var backgroundAlpha: Float = 1f
+        @Keep
+        set(value) {
+            field = value
+            background?.alpha = (value * 255).toInt()
+        }
+        @Keep get
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         settingsContainer = findViewById(R.id.settings_button_container)
@@ -117,4 +131,4 @@
 private const val TAG = "FooterActionsView"
 private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
 private val MotionEvent.string
-    get() = "($id): ($x,$y)"
\ No newline at end of file
+    get() = "($id): ($x,$y)"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 2416808..7a98081 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -557,9 +557,9 @@
     public void setQsExpansion(float expansion, float panelExpansionFraction,
             float proposedTranslation, float squishinessFraction) {
         float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
-        float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
+        float alphaProgress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
                 ? mFullShadeProgress : panelExpansionFraction;
-        setAlphaAnimationProgress(mInSplitShade ? progress : 1);
+        setAlphaAnimationProgress(mInSplitShade ? alphaProgress : 1);
         mContainer.setExpansion(expansion);
         final float translationScaleY = (mInSplitShade
                 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
@@ -600,7 +600,9 @@
         }
         mQSPanelController.setIsOnKeyguard(onKeyguard);
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
-        mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
+        float footerActionsExpansion =
+                onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
+        mQSFooterActionController.setExpansion(footerActionsExpansion);
         mQSPanelController.setRevealExpansion(expansion);
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 324c019..7155626 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -128,6 +128,8 @@
         if (mUsingMediaPlayer) {
             mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
             mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            mHorizontalLinearLayout.setVisibility(
+                    mUsingHorizontalLayout ? View.VISIBLE : View.GONE);
             mHorizontalLinearLayout.setClipChildren(false);
             mHorizontalLinearLayout.setClipToPadding(false);
 
@@ -445,6 +447,8 @@
         mMediaHostView = hostView;
         ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
         ViewGroup currentParent = (ViewGroup) hostView.getParent();
+        Log.d(getDumpableTag(), "Reattaching media host: " + horizontal
+                + ", current " + currentParent + ", new " + newParent);
         if (currentParent != newParent) {
             if (currentParent != null) {
                 currentParent.removeView(hostView);
@@ -589,6 +593,7 @@
 
     void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) {
         if (horizontal != mUsingHorizontalLayout || force) {
+            Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force);
             mUsingHorizontalLayout = horizontal;
             ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
             switchAllContentToParent(newParent, mTileLayout);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ec61ea6..6d5f844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -88,6 +88,8 @@
                 public void onConfigurationChange(Configuration newConfig) {
                     mShouldUseSplitNotificationShade =
                             LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+                    mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
+                            mView.getDumpableTag());
                     onConfigurationChanged();
                     if (newConfig.orientation != mLastOrientation) {
                         mLastOrientation = newConfig.orientation;
@@ -164,6 +166,7 @@
         mHost.addCallback(mQSHostCallback);
         setTiles();
         mLastOrientation = getResources().getConfiguration().orientation;
+        mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
         switchTileLayout(true);
 
         mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -171,6 +174,7 @@
 
     @Override
     protected void onViewDetached() {
+        mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag());
         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
         mHost.removeCallback(mQSHostCallback);
 
@@ -325,6 +329,8 @@
         /* Whether or not the panel currently contains a media player. */
         boolean horizontal = shouldUseHorizontalLayout();
         if (horizontal != mUsingHorizontalLayout || force) {
+            mQSLogger.logSwitchTileLayout(horizontal, mUsingHorizontalLayout, force,
+                    mView.getDumpableTag());
             mUsingHorizontalLayout = horizontal;
             mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
             updateMediaDisappearParameters();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index ab795fa..948fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -155,6 +155,54 @@
         })
     }
 
+    fun logOnViewAttached(orientation: Int, containerName: String) {
+        log(DEBUG, {
+            str1 = containerName
+            int1 = orientation
+        }, {
+            "onViewAttached: $str1 orientation $int1"
+        })
+    }
+
+    fun logOnViewDetached(orientation: Int, containerName: String) {
+        log(DEBUG, {
+            str1 = containerName
+            int1 = orientation
+        }, {
+            "onViewDetached: $str1 orientation $int1"
+        })
+    }
+
+    fun logOnConfigurationChanged(
+        lastOrientation: Int,
+        newOrientation: Int,
+        containerName: String
+    ) {
+        log(DEBUG, {
+            str1 = containerName
+            int1 = lastOrientation
+            int2 = newOrientation
+        }, {
+            "configuration change: $str1 orientation was $int1, now $int2"
+        })
+    }
+
+    fun logSwitchTileLayout(
+        after: Boolean,
+        before: Boolean,
+        force: Boolean,
+        containerName: String
+    ) {
+        log(DEBUG, {
+            str1 = containerName
+            bool1 = after
+            bool2 = before
+            bool3 = force
+        }, {
+            "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3"
+        })
+    }
+
     private fun toStateString(state: Int): String {
         return when (state) {
             Tile.STATE_ACTIVE -> "active"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index beb54c8..4397d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -18,7 +18,6 @@
 
 import android.net.Uri
 import android.util.Log
-import android.view.WindowManager.ScreenshotType
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
@@ -37,13 +36,12 @@
     private val controller: ScreenshotController,
 ) {
     fun processRequest(
-        @ScreenshotType type: Int,
-        onSavedListener: Consumer<Uri>,
         request: ScreenshotRequest,
+        onSavedListener: Consumer<Uri>,
         callback: RequestCallback
     ) {
 
-        if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+        if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
             val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
 
             controller.handleImageAsScreenshot(
@@ -53,12 +51,12 @@
             return
         }
 
-        when (type) {
+        when (request.type) {
             TAKE_SCREENSHOT_FULLSCREEN ->
                 controller.takeScreenshotFullscreen(null, onSavedListener, callback)
             TAKE_SCREENSHOT_SELECTED_REGION ->
                 controller.takeScreenshotPartial(null, onSavedListener, callback)
-            else -> Log.w(TAG, "Invalid screenshot option: $type")
+            else -> Log.w(TAG, "Invalid screenshot option: ${request.type}")
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f1f0223..7bf3217 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -229,11 +229,11 @@
 
         if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
             Log.d(TAG, "handleMessage: Using request processor");
-            mProcessor.processRequest(msg.what, uriConsumer, screenshotRequest, requestCallback);
+            mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback);
             return true;
         }
 
-        switch (msg.what) {
+        switch (screenshotRequest.getType()) {
             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7f0e76b..37a948d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -127,9 +127,7 @@
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
@@ -700,11 +698,7 @@
 
     private final CameraGestureHelper mCameraGestureHelper;
     private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
-    private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
-    private final Provider<SetKeyguardBottomAreaAlphaUseCase>
-            mSetKeyguardBottomAreaAlphaUseCaseProvider;
-    private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
-            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+    private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -776,10 +770,7 @@
             SystemClock systemClock,
             CameraGestureHelper cameraGestureHelper,
             Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
-            Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
-            Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
-            Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
-                    setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
+            Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
         super(view,
                 falsingManager,
                 dozeLog,
@@ -961,10 +952,7 @@
                     }
                 });
         mCameraGestureHelper = cameraGestureHelper;
-        mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
-        mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
-                setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+        mKeyguardBottomAreaInteractorProvider = keyguardBottomAreaInteractorProvider;
     }
 
     @VisibleForTesting
@@ -1475,7 +1463,7 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
-        mSetClockPositionUseCaseProvider.get().invoke(
+        mKeyguardBottomAreaInteractorProvider.get().setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -3249,7 +3237,7 @@
         float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
         mKeyguardBottomArea.setComponentAlphas(alpha);
-        mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
+        mKeyguardBottomAreaInteractorProvider.get().setAlpha(alpha);
         mLockIconViewController.setAlpha(alpha);
     }
 
@@ -3449,7 +3437,7 @@
 
     private void updateDozingVisibilities(boolean animate) {
         mKeyguardBottomArea.setDozing(mDozing, animate);
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+        mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
         if (!mDozing && animate) {
             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
         }
@@ -3752,7 +3740,7 @@
         mDozing = dozing;
         mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
         mKeyguardBottomArea.setDozing(mDozing, animate);
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+        mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
         mKeyguardStatusBarViewController.setDozing(mDozing);
 
         if (dozing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index decc02c..0898d63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -36,7 +36,7 @@
  * remove notifications that appear on screen for a period of time and dismiss themselves at the
  * appropriate time.  These include heads up notifications and ambient pulses.
  */
-public abstract class AlertingNotificationManager implements NotificationLifetimeExtender {
+public abstract class AlertingNotificationManager {
     private static final String TAG = "AlertNotifManager";
     protected final Clock mClock = new Clock();
     protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
@@ -47,14 +47,6 @@
         mHandler = handler;
     }
 
-    /**
-     * This is the list of entries that have already been removed from the
-     * NotificationManagerService side, but we keep it to prevent the UI from looking weird and
-     * will remove when possible. See {@link NotificationLifetimeExtender}
-     */
-    protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>();
-
-    protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
     protected int mMinimumDisplayTime;
     protected int mAutoDismissNotificationDecay;
     private final Handler mHandler;
@@ -209,12 +201,6 @@
         onAlertEntryRemoved(alertEntry);
         entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
         alertEntry.reset();
-        if (mExtendedLifetimeAlertEntries.contains(entry)) {
-            if (mNotificationLifetimeFinishedCallback != null) {
-                mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
-            }
-            mExtendedLifetimeAlertEntries.remove(entry);
-        }
     }
 
     /**
@@ -243,19 +229,6 @@
                 || alertEntry.mEntry.isRowDismissed();
     }
 
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // NotificationLifetimeExtender Methods
-
-    @Override
-    public void setCallback(NotificationSafeToRemoveCallback callback) {
-        mNotificationLifetimeFinishedCallback = callback;
-    }
-
-    @Override
-    public boolean shouldExtendLifetime(NotificationEntry entry) {
-        return !canRemoveImmediately(entry.getKey());
-    }
-
     /**
      * @param key
      * @return true if the entry is pinned
@@ -280,20 +253,6 @@
         return 0;
     }
 
-    @Override
-    public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
-        if (shouldExtend) {
-            mExtendedLifetimeAlertEntries.add(entry);
-            // We need to make sure that entries are stopping to alert eventually, let's remove
-            // this as soon as possible.
-            AlertEntry alertEntry = mAlertEntries.get(entry.getKey());
-            alertEntry.removeAsSoonAsPossible();
-        } else {
-            mExtendedLifetimeAlertEntries.remove(entry);
-        }
-    }
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-
     protected class AlertEntry implements Comparable<AlertEntry> {
         @Nullable public NotificationEntry mEntry;
         public long mPostTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ca14728..c983644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -27,6 +27,7 @@
 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;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -36,7 +37,6 @@
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
-import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -123,6 +123,8 @@
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
     private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+    public static final long DEFAULT_HIDE_DELAY_MS =
+            3500 + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -140,7 +142,6 @@
     protected final @Main DelayableExecutor mExecutor;
     protected final @Background DelayableExecutor mBackgroundExecutor;
     private final LockPatternUtils mLockPatternUtils;
-    private final IActivityManager mIActivityManager;
     private final FalsingManager mFalsingManager;
     private final KeyguardBypassController mKeyguardBypassController;
     private final AccessibilityManager mAccessibilityManager;
@@ -155,6 +156,7 @@
     private CharSequence mTrustGrantedIndication;
     private CharSequence mTransientIndication;
     private CharSequence mBiometricMessage;
+    private CharSequence mBiometricMessageFollowUp;
     protected ColorStateList mInitialTextColorState;
     private boolean mVisible;
     private boolean mOrganizationOwnedDevice;
@@ -171,7 +173,7 @@
     private int mBatteryLevel;
     private boolean mBatteryPresent = true;
     private long mChargingTimeRemaining;
-    private String mMessageToShowOnScreenOn;
+    private String mBiometricErrorMessageToShowOnScreenOn;
     private final Set<Integer> mCoExFaceHelpMsgIdsToShow;
     private boolean mInited;
 
@@ -189,11 +191,11 @@
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
-            if (mMessageToShowOnScreenOn != null) {
-                showBiometricMessage(mMessageToShowOnScreenOn);
+            if (mBiometricErrorMessageToShowOnScreenOn != null) {
+                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
                 // We want to keep this message around in case the screen was off
-                hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
-                mMessageToShowOnScreenOn = null;
+                hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
+                mBiometricErrorMessageToShowOnScreenOn = null;
             }
         }
     };
@@ -219,7 +221,6 @@
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
             ScreenLifecycle screenLifecycle,
-            IActivityManager iActivityManager,
             KeyguardBypassController keyguardBypassController,
             AccessibilityManager accessibilityManager) {
         mContext = context;
@@ -236,7 +237,6 @@
         mExecutor = executor;
         mBackgroundExecutor = bgExecutor;
         mLockPatternUtils = lockPatternUtils;
-        mIActivityManager = iActivityManager;
         mFalsingManager = falsingManager;
         mKeyguardBypassController = keyguardBypassController;
         mAccessibilityManager = accessibilityManager;
@@ -498,8 +498,23 @@
                             .build(),
                     true
             );
+            if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
+                mRotateTextViewController.updateIndication(
+                        INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                        new KeyguardIndication.Builder()
+                                .setMessage(mBiometricMessageFollowUp)
+                                .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+                                .setTextColor(mInitialTextColorState)
+                                .build(),
+                        true
+                );
+            } else {
+                mRotateTextViewController.hideIndication(
+                        INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
+            }
         } else {
             mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
         }
     }
 
@@ -719,38 +734,45 @@
     private void showTransientIndication(CharSequence transientIndication) {
         mTransientIndication = transientIndication;
         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
-        hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+        hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
 
         updateTransient();
     }
 
-    /**
-     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
-     */
-    public void showBiometricMessage(int biometricMessage) {
-        showBiometricMessage(mContext.getResources().getString(biometricMessage));
+    private void showBiometricMessage(CharSequence biometricMessage) {
+        showBiometricMessage(biometricMessage, null);
     }
 
     /**
-     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
+     * Shows {@param biometricMessage} and {@param biometricMessageFollowUp}
+     * until they are hidden by {@link #hideBiometricMessage}. Messages are rotated through
+     * by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message
+     * logic.
      */
-    private void showBiometricMessage(CharSequence biometricMessage) {
+    private void showBiometricMessage(CharSequence biometricMessage,
+            CharSequence biometricMessageFollowUp) {
         if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
             return;
         }
 
         mBiometricMessage = biometricMessage;
+        mBiometricMessageFollowUp = biometricMessageFollowUp;
 
         mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
         mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
-        hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+        hideBiometricMessageDelayed(
+                mBiometricMessageFollowUp != null
+                        ? DEFAULT_HIDE_DELAY_MS * 2
+                        : DEFAULT_HIDE_DELAY_MS
+        );
 
         updateBiometricMessage();
     }
 
     private void hideBiometricMessage() {
-        if (mBiometricMessage != null) {
+        if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
             mBiometricMessage = null;
+            mBiometricMessageFollowUp = null;
             mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
             updateBiometricMessage();
         }
@@ -789,9 +811,9 @@
             // colors can be hard to read in low brightness.
             mTopIndicationView.setTextColor(Color.WHITE);
 
-            CharSequence newIndication = null;
+            CharSequence newIndication;
             if (!TextUtils.isEmpty(mBiometricMessage)) {
-                newIndication = mBiometricMessage;
+                newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
             } else if (!TextUtils.isEmpty(mTransientIndication)) {
                 newIndication = mTransientIndication;
             } else if (!mBatteryPresent) {
@@ -909,15 +931,21 @@
                         || mAccessibilityManager.isTouchExplorationEnabled();
                 if (udfpsSupported && faceAuthenticated) { // co-ex
                     if (a11yEnabled) {
-                        showBiometricMessage(mContext.getString(
-                                R.string.keyguard_face_successful_unlock_swipe));
+                        showBiometricMessage(
+                                mContext.getString(R.string.keyguard_face_successful_unlock),
+                                mContext.getString(R.string.keyguard_unlock)
+                        );
                     } else {
-                        showBiometricMessage(mContext.getString(
-                                R.string.keyguard_face_successful_unlock_press));
+                        showBiometricMessage(
+                                mContext.getString(R.string.keyguard_face_successful_unlock),
+                                mContext.getString(R.string.keyguard_unlock_press)
+                        );
                     }
                 } else if (faceAuthenticated) { // face-only
-                    showBiometricMessage(mContext.getString(
-                            R.string.keyguard_face_successful_unlock_swipe));
+                    showBiometricMessage(
+                            mContext.getString(R.string.keyguard_face_successful_unlock),
+                            mContext.getString(R.string.keyguard_unlock)
+                    );
                 } else if (udfpsSupported) { // udfps-only
                     if (a11yEnabled) {
                         showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
@@ -943,10 +971,11 @@
         pw.println("  mPowerCharged: " + mPowerCharged);
         pw.println("  mChargingSpeed: " + mChargingSpeed);
         pw.println("  mChargingWattage: " + mChargingWattage);
-        pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
+        pw.println("  mMessageToShowOnScreenOn: " + mBiometricErrorMessageToShowOnScreenOn);
         pw.println("  mDozing: " + mDozing);
         pw.println("  mTransientIndication: " + mTransientIndication);
         pw.println("  mBiometricMessage: " + mBiometricMessage);
+        pw.println("  mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
         pw.println("  AOD text: " + (
@@ -958,8 +987,6 @@
     }
 
     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
-        public static final int HIDE_DELAY_MS = 5000;
-
         @Override
         public void onTimeChanged() {
             if (mVisible) {
@@ -1077,7 +1104,7 @@
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
                 showBiometricMessage(errString);
             } else {
-                mMessageToShowOnScreenOn = errString;
+                mBiometricErrorMessageToShowOnScreenOn = errString;
             }
         }
 
@@ -1139,7 +1166,7 @@
                 // Let's hide any previous messages when authentication starts, otherwise
                 // multiple auth attempts would overlap.
                 hideBiometricMessage();
-                mMessageToShowOnScreenOn = null;
+                mBiometricErrorMessageToShowOnScreenOn = null;
             }
         }
 
@@ -1179,7 +1206,7 @@
         @Override
         public void onRequireUnlockForNfc() {
             showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
-            hideTransientIndicationDelayed(HIDE_DELAY_MS);
+            hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
deleted file mode 100644
index 48e2923..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.android.systemui.statusbar;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Interface for anything that may need to keep notifications managed even after
- * {@link NotificationListener} removes it.  The lifetime extender is in charge of performing the
- * callback when the notification is then safe to remove.
- */
-public interface NotificationLifetimeExtender {
-
-    /**
-     * Set the handler to callback to when the notification is safe to remove.
-     *
-     * @param callback the handler to callback
-     */
-    void setCallback(@NonNull NotificationSafeToRemoveCallback callback);
-
-    /**
-     * Determines whether or not the extender needs the notification kept after removal.
-     *
-     * @param entry the entry containing the notification to check
-     * @return true if the notification lifetime should be extended
-     */
-    boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
-
-    /**
-     * It's possible that a notification was canceled before it ever became visible. This callback
-     * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
-     * service is canceled too quickly but we still want to make sure a FGS notification shows.
-     * @param pendingEntry the canceled (but pending) entry
-     * @return true if the notification lifetime should be extended
-     */
-    default boolean shouldExtendLifetimeForPendingNotification(
-            @NonNull NotificationEntry pendingEntry) {
-        return false;
-    }
-
-    /**
-     * Sets whether or not the lifetime should be managed by the extender.  In practice, if
-     * shouldManage is true, this is where the extender starts managing the entry internally and is
-     * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
-     * when the entry is safe to remove.  If shouldManage is false, the extender no longer needs to
-     * worry about it (either because we will be removing it anyway or the entry is no longer
-     * removed due to an update).
-     *
-     * @param entry the entry that needs an extended lifetime
-     * @param shouldManage true if the extender should manage the entry now, false otherwise
-     */
-    void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage);
-
-    /**
-     * The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
-     */
-    interface NotificationSafeToRemoveCallback {
-        /**
-         * Called when the lifetime extender determines it's safe to remove.
-         *
-         * @param key key of the entry that is now safe to remove
-         */
-        void onSafeToRemove(String key);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 78b3b0a..d74d408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -32,7 +31,6 @@
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
@@ -47,12 +45,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
@@ -75,7 +71,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Consumer;
 
 import dagger.Lazy;
@@ -100,8 +95,6 @@
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SmartReplyController mSmartReplyController;
     private final NotificationVisibilityProvider mVisibilityProvider;
-    private final NotificationEntryManager mEntryManager;
-    private final Handler mMainHandler;
     private final ActionClickLogger mLogger;
 
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -110,7 +103,6 @@
     protected final NotifPipelineFlags mNotifPipelineFlags;
     private final UserManager mUserManager;
     private final KeyguardManager mKeyguardManager;
-    private final RemoteInputNotificationRebuilder mRebuilder;
     private final StatusBarStateController mStatusBarStateController;
     private final RemoteInputUriController mRemoteInputUriController;
     private final NotificationClickNotifier mClickNotifier;
@@ -265,10 +257,8 @@
             SmartReplyController smartReplyController,
             NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager notificationEntryManager,
-            RemoteInputNotificationRebuilder rebuilder,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             StatusBarStateController statusBarStateController,
-            @Main Handler mainHandler,
             RemoteInputUriController remoteInputUriController,
             NotificationClickNotifier clickNotifier,
             ActionClickLogger logger,
@@ -278,14 +268,11 @@
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
         mVisibilityProvider = visibilityProvider;
-        mEntryManager = notificationEntryManager;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
-        mMainHandler = mainHandler;
         mLogger = logger;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mRebuilder = rebuilder;
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mStatusBarStateController = statusBarStateController;
         mRemoteInputUriController = remoteInputUriController;
@@ -788,238 +775,4 @@
         /** Called when the RemoteInputController is attached to the manager */
         void setRemoteInputController(@NonNull RemoteInputController remoteInputController);
     }
-
-    @VisibleForTesting
-    protected class LegacyRemoteInputLifetimeExtender implements RemoteInputListener, Dumpable {
-
-        /**
-         * How long to wait before auto-dismissing a notification that was kept for remote input,
-         * and has now sent a remote input. We auto-dismiss, because the app may not see a reason to
-         * cancel these given that they technically don't exist anymore. We wait a bit in case the
-         * app issues an update.
-         */
-        private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
-
-        /**
-         * Notifications that are already removed but are kept around because we want to show the
-         * remote input history. See {@link RemoteInputHistoryExtender} and
-         * {@link SmartReplyHistoryExtender}.
-         */
-        protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>();
-
-        /**
-         * Notifications that are already removed but are kept around because the remote input is
-         * actively being used (i.e. user is typing in it).  See {@link RemoteInputActiveExtender}.
-         */
-        protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
-                new ArraySet<>();
-
-        protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
-                mNotificationLifetimeFinishedCallback;
-
-        protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders =
-                new ArrayList<>();
-        private RemoteInputController mRemoteInputController;
-
-        LegacyRemoteInputLifetimeExtender() {
-            addLifetimeExtenders();
-        }
-
-        /**
-         * Adds all the notification lifetime extenders. Each extender represents a reason for the
-         * NotificationRemoteInputManager to keep a notification lifetime extended.
-         */
-        protected void addLifetimeExtenders() {
-            mLifetimeExtenders.add(new RemoteInputHistoryExtender());
-            mLifetimeExtenders.add(new SmartReplyHistoryExtender());
-            mLifetimeExtenders.add(new RemoteInputActiveExtender());
-        }
-
-        @Override
-        public void setRemoteInputController(@NonNull RemoteInputController remoteInputController) {
-            mRemoteInputController= remoteInputController;
-        }
-
-        @Override
-        public void onRemoteInputSent(@NonNull NotificationEntry entry) {
-            if (FORCE_REMOTE_INPUT_HISTORY
-                    && isNotificationKeptForRemoteInputHistory(entry.getKey())) {
-                mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
-            } else if (mEntriesKeptForRemoteInputActive.contains(entry)) {
-                // We're currently holding onto this notification, but from the apps point of
-                // view it is already canceled, so we'll need to cancel it on the apps behalf
-                // after sending - unless the app posts an update in the mean time, so wait a
-                // bit.
-                mMainHandler.postDelayed(() -> {
-                    if (mEntriesKeptForRemoteInputActive.remove(entry)) {
-                        mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
-                    }
-                }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
-            }
-        }
-
-        @Override
-        public void onPanelCollapsed() {
-            for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
-                NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
-                if (mRemoteInputController != null) {
-                    mRemoteInputController.removeRemoteInput(entry, null);
-                }
-                if (mNotificationLifetimeFinishedCallback != null) {
-                    mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
-                }
-            }
-            mEntriesKeptForRemoteInputActive.clear();
-        }
-
-        @Override
-        public boolean isNotificationKeptForRemoteInputHistory(@NonNull String key) {
-            return mKeysKeptForRemoteInputHistory.contains(key);
-        }
-
-        @Override
-        public void releaseNotificationIfKeptForRemoteInputHistory(
-                @NonNull NotificationEntry entry) {
-            final String key = entry.getKey();
-            if (isNotificationKeptForRemoteInputHistory(key)) {
-                mMainHandler.postDelayed(() -> {
-                    if (isNotificationKeptForRemoteInputHistory(key)) {
-                        mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
-                    }
-                }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
-            }
-        }
-
-        @VisibleForTesting
-        public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
-            return mEntriesKeptForRemoteInputActive;
-        }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw,
-                @NonNull String[] args) {
-            pw.println("LegacyRemoteInputLifetimeExtender:");
-            pw.print("  mKeysKeptForRemoteInputHistory: ");
-            pw.println(mKeysKeptForRemoteInputHistory);
-            pw.print("  mEntriesKeptForRemoteInputActive: ");
-            pw.println(mEntriesKeptForRemoteInputActive);
-        }
-
-        /**
-         * NotificationRemoteInputManager has multiple reasons to keep notification lifetime
-         * extended so we implement multiple NotificationLifetimeExtenders
-         */
-        protected abstract class RemoteInputExtender implements NotificationLifetimeExtender {
-            @Override
-            public void setCallback(NotificationSafeToRemoveCallback callback) {
-                if (mNotificationLifetimeFinishedCallback == null) {
-                    mNotificationLifetimeFinishedCallback = callback;
-                }
-            }
-        }
-
-        /**
-         * Notification is kept alive as it was cancelled in response to a remote input interaction.
-         * This allows us to show what you replied and allows you to continue typing into it.
-         */
-        protected class RemoteInputHistoryExtender extends RemoteInputExtender {
-            @Override
-            public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
-                return shouldKeepForRemoteInputHistory(entry);
-            }
-
-            @Override
-            public void setShouldManageLifetime(NotificationEntry entry,
-                    boolean shouldExtend) {
-                if (shouldExtend) {
-                    StatusBarNotification newSbn = mRebuilder.rebuildForRemoteInputReply(entry);
-                    entry.onRemoteInputInserted();
-
-                    if (newSbn == null) {
-                        return;
-                    }
-
-                    mEntryManager.updateNotification(newSbn, null);
-
-                    // Ensure the entry hasn't already been removed. This can happen if there is an
-                    // inflation exception while updating the remote history
-                    if (entry.isRemoved()) {
-                        return;
-                    }
-
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Keeping notification around after sending remote input "
-                                + entry.getKey());
-                    }
-
-                    mKeysKeptForRemoteInputHistory.add(entry.getKey());
-                } else {
-                    mKeysKeptForRemoteInputHistory.remove(entry.getKey());
-                }
-            }
-        }
-
-        /**
-         * Notification is kept alive for smart reply history.  Similar to REMOTE_INPUT_HISTORY but
-         * with {@link SmartReplyController} specific logic
-         */
-        protected class SmartReplyHistoryExtender extends RemoteInputExtender {
-            @Override
-            public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
-                return shouldKeepForSmartReplyHistory(entry);
-            }
-
-            @Override
-            public void setShouldManageLifetime(NotificationEntry entry,
-                    boolean shouldExtend) {
-                if (shouldExtend) {
-                    StatusBarNotification newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry);
-
-                    if (newSbn == null) {
-                        return;
-                    }
-
-                    mEntryManager.updateNotification(newSbn, null);
-
-                    if (entry.isRemoved()) {
-                        return;
-                    }
-
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Keeping notification around after sending smart reply "
-                                + entry.getKey());
-                    }
-
-                    mKeysKeptForRemoteInputHistory.add(entry.getKey());
-                } else {
-                    mKeysKeptForRemoteInputHistory.remove(entry.getKey());
-                    mSmartReplyController.stopSending(entry);
-                }
-            }
-        }
-
-        /**
-         * Notification is kept alive because the user is still using the remote input
-         */
-        protected class RemoteInputActiveExtender extends RemoteInputExtender {
-            @Override
-            public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
-                return isRemoteInputActive(entry);
-            }
-
-            @Override
-            public void setShouldManageLifetime(NotificationEntry entry,
-                    boolean shouldExtend) {
-                if (shouldExtend) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Keeping notification around while remote input active "
-                                + entry.getKey());
-                    }
-                    mEntriesKeptForRemoteInputActive.add(entry);
-                } else {
-                    mEntriesKeptForRemoteInputActive.remove(entry);
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 0951e82..8752f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -115,10 +115,8 @@
                 smartReplyController,
                 visibilityProvider,
                 notificationEntryManager,
-                rebuilder,
                 centralSurfacesOptionalLazy,
                 statusBarStateController,
-                mainHandler,
                 remoteInputUriController,
                 clickNotifier,
                 actionClickLogger,
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 7583a98..e2f87b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -15,66 +15,22 @@
  */
 package com.android.systemui.statusbar.notification;
 
-import static android.service.notification.NotificationListenerService.REASON_ERROR;
-
-import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRankerStub;
-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.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.util.Assert;
-import com.android.systemui.util.Compile;
-import com.android.systemui.util.leak.LeakDetector;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-import dagger.Lazy;
 
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of
@@ -90,33 +46,10 @@
  * inflated, an entry moves into the active state, where it _could_ potentially be shown to the
  * user. After an entry makes its way into the active state, we sort and filter the entire set to
  * repopulate the visible set.
- *
- * There are a few different things that other classes may be interested in, and most of them
- * involve the current set of notifications. Here's a brief overview of things you may want to know:
- * @see #getVisibleNotifications() for the visible set
- * @see #getActiveNotificationUnfiltered(String) to check if a key exists
- * @see #getPendingNotificationsIterator() for an iterator over the pending notifications
- * @see #getPendingOrActiveNotif(String) to find a notification exists for that key in any list
- * @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns
  */
-public class NotificationEntryManager implements
-        CommonNotifCollection,
-        Dumpable,
-        VisualStabilityManager.Callback {
+public class NotificationEntryManager implements VisualStabilityManager.Callback {
 
     private final NotificationEntryManagerLogger mLogger;
-    private final NotificationGroupManagerLegacy mGroupManager;
-    private final NotifPipelineFlags mNotifPipelineFlags;
-    private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
-    private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
-    private final LeakDetector mLeakDetector;
-    private final IStatusBarService mStatusBarService;
-    private final DumpManager mDumpManager;
-    private final Executor mBgExecutor;
-
-    private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
-    private final Set<NotificationEntry> mReadOnlyAllNotifications =
-            Collections.unmodifiableSet(mAllNotifications);
 
     /** Pending notifications are ones awaiting inflation */
     @VisibleForTesting
@@ -129,96 +62,14 @@
     /** This is the list of "active notifications for this user in this context" */
     @VisibleForTesting
     protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
-    private final List<NotificationEntry> mReadOnlyNotifications =
-            Collections.unmodifiableList(mSortedAndFiltered);
-
-    private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
-            new ArrayMap<>();
-
     private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
-
-    private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
-    private RankingMap mLatestRankingMap;
-
-    @VisibleForTesting
-    final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
-            = new ArrayList<>();
     private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
-    private final List<NotificationRemoveInterceptor> mRemoveInterceptors = new ArrayList<>();
 
     /**
      * Injected constructor. See {@link NotificationsModule}.
      */
-    public NotificationEntryManager(
-            NotificationEntryManagerLogger logger,
-            NotificationGroupManagerLegacy groupManager,
-            NotifPipelineFlags notifPipelineFlags,
-            Lazy<NotificationRowBinder> notificationRowBinderLazy,
-            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
-            LeakDetector leakDetector,
-            IStatusBarService statusBarService,
-            DumpManager dumpManager,
-            @Background Executor bgExecutor
-    ) {
+    public NotificationEntryManager(NotificationEntryManagerLogger logger) {
         mLogger = logger;
-        mGroupManager = groupManager;
-        mNotifPipelineFlags = notifPipelineFlags;
-        mNotificationRowBinderLazy = notificationRowBinderLazy;
-        mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
-        mLeakDetector = leakDetector;
-        mStatusBarService = statusBarService;
-        mDumpManager = dumpManager;
-        mBgExecutor = bgExecutor;
-    }
-
-    /** Once called, the NEM will start processing notification events from system server. */
-    public void initialize(
-            NotificationListener notificationListener,
-            LegacyNotificationRanker ranker) {
-        mRanker = ranker;
-        notificationListener.addNotificationHandler(mNotifListener);
-        mDumpManager.registerDumpable(this);
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("NotificationEntryManager state:");
-        pw.println("  mAllNotifications=");
-        if (mAllNotifications.size() == 0) {
-            pw.println("null");
-        } else {
-            int i = 0;
-            for (NotificationEntry entry : mAllNotifications) {
-                dumpEntry(pw, "  ", i, entry);
-                i++;
-            }
-        }
-        pw.print("  mPendingNotifications=");
-        if (mPendingNotifications.size() == 0) {
-            pw.println("null");
-        } else {
-            for (NotificationEntry entry : mPendingNotifications.values()) {
-                pw.println(entry.getSbn());
-            }
-        }
-        pw.println("  Remove interceptors registered:");
-        for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
-            pw.println("    " + interceptor.getClass().getSimpleName());
-        }
-        pw.println("  Lifetime extenders registered:");
-        for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
-            pw.println("    " + extender.getClass().getSimpleName());
-        }
-        pw.println("  Lifetime-extended notifications:");
-        if (mRetainedNotifications.isEmpty()) {
-            pw.println("    None");
-        } else {
-            for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry
-                    : mRetainedNotifications.entrySet()) {
-                pw.println("    " + entry.getKey().getSbn() + " retained by "
-                        + entry.getValue().getClass().getName());
-            }
-        }
     }
 
     /** Adds a {@link NotificationEntryListener}. */
@@ -234,501 +85,12 @@
         mNotificationEntryListeners.remove(listener);
     }
 
-    /** Add a {@link NotificationRemoveInterceptor}. */
-    public void addNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
-        mRemoveInterceptors.add(interceptor);
-    }
-
-    /** Remove a {@link NotificationRemoveInterceptor} */
-    public void removeNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
-        mRemoveInterceptors.remove(interceptor);
-    }
-
-    /** Adds multiple {@link NotificationLifetimeExtender}s. */
-    public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
-        for (NotificationLifetimeExtender extender : extenders) {
-            addNotificationLifetimeExtender(extender);
-        }
-    }
-
-    /** Adds a {@link NotificationLifetimeExtender}. */
-    public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
-        mNotificationLifetimeExtenders.add(extender);
-        extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
-                UNDEFINED_DISMISS_REASON));
-    }
-
     @Override
     public void onChangeAllowed() {
         updateNotifications("reordering is now allowed");
     }
 
     /**
-     * User requests a notification to be removed.
-     *
-     * @param n the notification to remove.
-     * @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
-     *               or 0 if unknown.
-     */
-    public void performRemoveNotification(
-            StatusBarNotification n,
-            @NonNull DismissedByUserStats stats,
-            int reason
-    ) {
-        removeNotificationInternal(
-                n.getKey(),
-                null,
-                stats.notificationVisibility,
-                false /* forceRemove */,
-                stats,
-                reason);
-    }
-
-    private NotificationVisibility obtainVisibility(String key) {
-        NotificationEntry e = mActiveNotifications.get(key);
-        final int rank;
-        if (e != null) {
-            rank = e.getRanking().getRank();
-        } else {
-            rank = 0;
-        }
-
-        final int count = mActiveNotifications.size();
-        NotificationVisibility.NotificationLocation location =
-                NotificationLogger.getNotificationLocation(getActiveNotificationUnfiltered(key));
-        return NotificationVisibility.obtain(key, rank, count, true, location);
-    }
-
-    private void abortExistingInflation(String key, String reason) {
-        if (mPendingNotifications.containsKey(key)) {
-            NotificationEntry entry = mPendingNotifications.get(key);
-            entry.abortTask();
-            mPendingNotifications.remove(key);
-            mLogger.logInflationAborted(key, "pending", reason);
-        }
-        NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
-        if (addedEntry != null) {
-            addedEntry.abortTask();
-            mLogger.logInflationAborted(key, "active", reason);
-        }
-    }
-
-    /**
-     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
-     * about the failure.
-     *
-     * WARNING: this will call back into us.  Don't hold any locks.
-     */
-    private void handleInflationException(StatusBarNotification n, Exception e) {
-        removeNotificationInternal(
-                n.getKey(),
-                null,
-                null,
-                true /* forceRemove */,
-                null /* dismissedByUserStats */,
-                REASON_ERROR);
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onInflationError(n, e);
-        }
-    }
-
-    private final InflationCallback mInflationCallback = new InflationCallback() {
-        @Override
-        public void handleInflationException(NotificationEntry entry, Exception e) {
-            Trace.beginSection("NotificationEntryManager.handleInflationException");
-            NotificationEntryManager.this.handleInflationException(entry.getSbn(), e);
-            Trace.endSection();
-        }
-
-        @Override
-        public void onAsyncInflationFinished(NotificationEntry entry) {
-            Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished");
-            mPendingNotifications.remove(entry.getKey());
-            // If there was an async task started after the removal, we don't want to add it back to
-            // the list, otherwise we might get leaks.
-            if (!entry.isRowRemoved()) {
-                boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
-                mLogger.logNotifInflated(entry.getKey(), isNew);
-                if (isNew) {
-                    for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                        listener.onEntryInflated(entry);
-                    }
-                    addActiveNotification(entry);
-                    updateNotifications("onAsyncInflationFinished");
-                    for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                        listener.onNotificationAdded(entry);
-                    }
-                } else {
-                    for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                        listener.onEntryReinflated(entry);
-                    }
-                }
-            }
-            Trace.endSection();
-        }
-    };
-
-    private final NotificationHandler mNotifListener = new NotificationHandler() {
-        @Override
-        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
-            final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
-            if (isUpdateToInflatedNotif) {
-                updateNotification(sbn, rankingMap);
-            } else {
-                addNotification(sbn, rankingMap);
-            }
-        }
-
-        @Override
-        public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
-            removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
-        }
-
-        @Override
-        public void onNotificationRemoved(
-                StatusBarNotification sbn,
-                RankingMap rankingMap,
-                int reason) {
-            removeNotification(sbn.getKey(), rankingMap, reason);
-        }
-
-        @Override
-        public void onNotificationRankingUpdate(RankingMap rankingMap) {
-            updateNotificationRanking(rankingMap);
-        }
-
-        @Override
-        public void onNotificationsInitialized() {
-        }
-
-        @Override
-        public void onNotificationChannelModified(
-                String pkgName,
-                UserHandle user,
-                NotificationChannel channel,
-                int modificationType) {
-            notifyChannelModified(pkgName, user, channel, modificationType);
-        }
-    };
-
-    /**
-     * Equivalent to the old NotificationData#add
-     * @param entry - an entry which is prepared for display
-     */
-    private void addActiveNotification(NotificationEntry entry) {
-        Assert.isMainThread();
-
-        mActiveNotifications.put(entry.getKey(), entry);
-        mGroupManager.onEntryAdded(entry);
-        updateRankingAndSort(mRanker.getRankingMap(), "addEntryInternalInternal");
-    }
-
-    /**
-     * Available so that tests can directly manipulate the list of active notifications easily
-     *
-     * @param entry the entry to add directly to the visible notification map
-     */
-    @VisibleForTesting
-    public void addActiveNotificationForTest(NotificationEntry entry) {
-        mActiveNotifications.put(entry.getKey(), entry);
-        mGroupManager.onEntryAdded(entry);
-
-        reapplyFilterAndSort("addVisibleNotification");
-    }
-
-    @VisibleForTesting
-    protected void removeNotification(String key, RankingMap ranking, int reason) {
-        removeNotificationInternal(
-                key,
-                ranking,
-                obtainVisibility(key),
-                false /* forceRemove */,
-                null /* dismissedByUserStats */,
-                reason);
-    }
-
-    /**
-     * Internally remove a notification because system server has reported the notification
-     * should be removed OR the user has manually dismissed the notification
-     * @param dismissedByUserStats non-null if the user manually dismissed the notification
-     */
-    private void removeNotificationInternal(
-            String key,
-            @Nullable RankingMap ranking,
-            @Nullable NotificationVisibility visibility,
-            boolean forceRemove,
-            DismissedByUserStats dismissedByUserStats,
-            int reason) {
-        Trace.beginSection("NotificationEntryManager.removeNotificationInternal");
-
-        final NotificationEntry entry = getActiveNotificationUnfiltered(key);
-
-        for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
-            if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
-                // Remove intercepted; log and skip
-                mLogger.logRemovalIntercepted(key);
-                Trace.endSection();
-                return;
-            }
-        }
-
-        boolean lifetimeExtended = false;
-
-        // Notification was canceled before it got inflated
-        if (entry == null) {
-            NotificationEntry pendingEntry = mPendingNotifications.get(key);
-            if (pendingEntry != null) {
-                for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
-                    if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
-                        extendLifetime(pendingEntry, extender);
-                        lifetimeExtended = true;
-                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending");
-                    }
-                }
-                if (!lifetimeExtended) {
-                    // At this point, we are guaranteed the notification will be removed
-                    abortExistingInflation(key, "removeNotification");
-                    // Fix for b/201097913: NotifCollectionListener#onEntryRemoved specifies that
-                    //   #onEntryRemoved should be called when a notification is cancelled,
-                    //   regardless of whether the notification was pending or active.
-                    // Note that mNotificationEntryListeners are NOT notified of #onEntryRemoved
-                    //   because for that interface, #onEntryRemoved should only be called for
-                    //   active entries, NOT pending ones.
-                    for (NotifCollectionListener listener : mNotifCollectionListeners) {
-                        listener.onEntryRemoved(pendingEntry, REASON_UNKNOWN);
-                    }
-                    for (NotifCollectionListener listener : mNotifCollectionListeners) {
-                        listener.onEntryCleanUp(pendingEntry);
-                    }
-                    mAllNotifications.remove(pendingEntry);
-                    mLeakDetector.trackGarbage(pendingEntry);
-                }
-            }
-        } else {
-            // If a manager needs to keep the notification around for whatever reason, we
-            // keep the notification
-            boolean entryDismissed = entry.isRowDismissed();
-            if (!forceRemove && !entryDismissed) {
-                for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
-                    if (extender.shouldExtendLifetime(entry)) {
-                        mLatestRankingMap = ranking;
-                        extendLifetime(entry, extender);
-                        lifetimeExtended = true;
-                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active");
-                        break;
-                    }
-                }
-            }
-
-            if (!lifetimeExtended) {
-                // At this point, we are guaranteed the notification will be removed
-                abortExistingInflation(key, "removeNotification");
-                mAllNotifications.remove(entry);
-
-                // Ensure any managers keeping the lifetime extended stop managing the entry
-                cancelLifetimeExtension(entry);
-
-                if (entry.rowExists()) {
-                    entry.removeRow();
-                }
-
-                // Let's remove the children if this was a summary
-                handleGroupSummaryRemoved(key);
-                removeVisibleNotification(key);
-                updateNotifications("removeNotificationInternal");
-                final boolean removedByUser = dismissedByUserStats != null;
-
-                mLogger.logNotifRemoved(entry.getKey(), removedByUser);
-                if (removedByUser && visibility != null) {
-                    sendNotificationRemovalToServer(entry.getSbn(), dismissedByUserStats);
-                }
-                for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                    listener.onEntryRemoved(entry, visibility, removedByUser, reason);
-                }
-                for (NotifCollectionListener listener : mNotifCollectionListeners) {
-                    // NEM doesn't have a good knowledge of reasons so defaulting to unknown.
-                    listener.onEntryRemoved(entry, REASON_UNKNOWN);
-                }
-                for (NotifCollectionListener listener : mNotifCollectionListeners) {
-                    listener.onEntryCleanUp(entry);
-                }
-                mLeakDetector.trackGarbage(entry);
-            }
-        }
-        Trace.endSection();
-    }
-
-    private void sendNotificationRemovalToServer(
-            StatusBarNotification notification,
-            DismissedByUserStats dismissedByUserStats) {
-        mBgExecutor.execute(() -> {
-            try {
-                mStatusBarService.onNotificationClear(
-                        notification.getPackageName(),
-                        notification.getUser().getIdentifier(),
-                        notification.getKey(),
-                        dismissedByUserStats.dismissalSurface,
-                        dismissedByUserStats.dismissalSentiment,
-                        dismissedByUserStats.notificationVisibility);
-            } catch (RemoteException ex) {
-                // system process is dead if we're here.
-            }
-        });
-    }
-
-    /**
-     * Ensures that the group children are cancelled immediately when the group summary is cancelled
-     * instead of waiting for the notification manager to send all cancels. Otherwise this could
-     * lead to flickers.
-     *
-     * This also ensures that the animation looks nice and only consists of a single disappear
-     * animation instead of multiple.
-     *  @param key the key of the notification was removed
-     *
-     */
-    private void handleGroupSummaryRemoved(String key) {
-        NotificationEntry entry = getActiveNotificationUnfiltered(key);
-        if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
-            if (entry.getSbn().getOverrideGroupKey() != null && !entry.isRowDismissed()) {
-                // We don't want to remove children for autobundled notifications as they are not
-                // always cancelled. We only remove them if they were dismissed by the user.
-                return;
-            }
-            List<NotificationEntry> childEntries = entry.getAttachedNotifChildren();
-            if (childEntries == null) {
-                return;
-            }
-            for (int i = 0; i < childEntries.size(); i++) {
-                NotificationEntry childEntry = childEntries.get(i);
-                boolean isForeground = (entry.getSbn().getNotification().flags
-                        & Notification.FLAG_FOREGROUND_SERVICE) != 0;
-                boolean keepForReply =
-                        mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
-                        || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
-                if (isForeground || keepForReply) {
-                    // the child is a foreground service notification which we can't remove or it's
-                    // a child we're keeping around for reply!
-                    continue;
-                }
-                childEntry.setKeepInParent(true);
-                // we need to set this state earlier as otherwise we might generate some weird
-                // animations
-                childEntry.removeRow();
-            }
-        }
-    }
-
-    private void addNotificationInternal(
-            StatusBarNotification notification,
-            RankingMap rankingMap) throws InflationException {
-        Trace.beginSection("NotificationEntryManager.addNotificationInternal");
-        String key = notification.getKey();
-        if (DEBUG) {
-            Log.d(TAG, "addNotification key=" + key);
-        }
-
-        updateRankingAndSort(rankingMap, "addNotificationInternal");
-
-        Ranking ranking = new Ranking();
-        rankingMap.getRanking(key, ranking);
-
-        NotificationEntry entry = mPendingNotifications.get(key);
-        if (entry != null) {
-            entry.setSbn(notification);
-            entry.setRanking(ranking);
-        } else {
-            entry = new NotificationEntry(
-                    notification,
-                    ranking,
-                    SystemClock.uptimeMillis());
-            mAllNotifications.add(entry);
-            mLeakDetector.trackInstance(entry);
-
-            for (NotifCollectionListener listener : mNotifCollectionListeners) {
-                listener.onEntryInit(entry);
-            }
-        }
-
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryBind(entry, notification);
-        }
-
-        mPendingNotifications.put(key, entry);
-        mLogger.logNotifAdded(entry.getKey());
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onPendingEntryAdded(entry);
-        }
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryAdded(entry);
-        }
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingApplied();
-        }
-        Trace.endSection();
-    }
-
-    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
-        try {
-            addNotificationInternal(notification, ranking);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
-        }
-    }
-
-    private void updateNotificationInternal(StatusBarNotification notification,
-            RankingMap ranking) throws InflationException {
-        Trace.beginSection("NotificationEntryManager.updateNotificationInternal");
-        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
-        final String key = notification.getKey();
-        abortExistingInflation(key, "updateNotification");
-        final NotificationEntry entry = getActiveNotificationUnfiltered(key);
-        if (entry == null) {
-            Trace.endSection();
-            return;
-        }
-
-        // Notification is updated so it is essentially re-added and thus alive again.  Don't need
-        // to keep its lifetime extended.
-        cancelLifetimeExtension(entry);
-
-        updateRankingAndSort(ranking, "updateNotificationInternal");
-        StatusBarNotification oldSbn = entry.getSbn();
-        entry.setSbn(notification);
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryBind(entry, notification);
-        }
-        mGroupManager.onEntryUpdated(entry, oldSbn);
-
-        mLogger.logNotifUpdated(entry.getKey());
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onPreEntryUpdated(entry);
-        }
-        final boolean fromSystem = ranking != null;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryUpdated(entry, fromSystem);
-        }
-
-        updateNotifications("updateNotificationInternal");
-
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onPostEntryUpdated(entry);
-        }
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingApplied();
-        }
-        Trace.endSection();
-    }
-
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
-        try {
-            updateNotificationInternal(notification, ranking);
-        } catch (InflationException e) {
-            handleInflationException(notification, e);
-        }
-    }
-
-    /**
      * Update the notifications
      * @param reason why the notifications are updating
      */
@@ -736,128 +98,6 @@
         mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
     }
 
-    public void updateNotificationRanking(RankingMap rankingMap) {
-        Trace.beginSection("NotificationEntryManager.updateNotificationRanking");
-        List<NotificationEntry> entries = new ArrayList<>();
-        entries.addAll(getVisibleNotifications());
-        entries.addAll(mPendingNotifications.values());
-
-        // Has a copy of the current UI adjustments.
-        ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
-        ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
-        for (NotificationEntry entry : entries) {
-            NotificationUiAdjustment adjustment =
-                    NotificationUiAdjustment.extractFromNotificationEntry(entry);
-            oldAdjustments.put(entry.getKey(), adjustment);
-            oldImportances.put(entry.getKey(), entry.getImportance());
-        }
-
-        // Populate notification entries from the new rankings.
-        updateRankingAndSort(rankingMap, "updateNotificationRanking");
-        updateRankingOfPendingNotifications(rankingMap);
-
-        // By comparing the old and new UI adjustments, reinflate the view accordingly.
-        for (NotificationEntry entry : entries) {
-            mNotificationRowBinderLazy.get()
-                    .onNotificationRankingUpdated(
-                            entry,
-                            oldImportances.get(entry.getKey()),
-                            oldAdjustments.get(entry.getKey()),
-                            NotificationUiAdjustment.extractFromNotificationEntry(entry),
-                            mInflationCallback);
-        }
-
-        updateNotifications("updateNotificationRanking");
-
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onNotificationRankingUpdated(rankingMap);
-        }
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingUpdate(rankingMap);
-        }
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingApplied();
-        }
-        Trace.endSection();
-    }
-
-    void notifyChannelModified(
-            String pkgName,
-            UserHandle user,
-            NotificationChannel channel,
-            int modificationType) {
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
-        }
-        for (NotificationEntryListener listener : mNotificationEntryListeners) {
-            listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
-        }
-    }
-
-    private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
-        if (rankingMap == null) {
-            return;
-        }
-        for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
-            Ranking ranking = new Ranking();
-            if (rankingMap.getRanking(pendingNotification.getKey(), ranking)) {
-                pendingNotification.setRanking(ranking);
-            }
-        }
-    }
-
-    /**
-     * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
-     * notifications whose views have not yet been inflated. In general, the system pretends like
-     * these don't exist, although there are a couple exceptions.
-     */
-    public Iterable<NotificationEntry> getPendingNotificationsIterator() {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mPendingNotifications.values();
-    }
-
-    /**
-     * Use this method to retrieve a notification entry that has been prepared for presentation.
-     * Note that the notification may be filtered out and never shown to the user.
-     *
-     * @see #getVisibleNotifications() for the currently sorted and filtered list
-     *
-     * @return a {@link NotificationEntry} if it has been prepared, else null
-     */
-    public NotificationEntry getActiveNotificationUnfiltered(String key) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mActiveNotifications.get(key);
-    }
-
-    /**
-     * Gets the pending or visible notification entry with the given key. Returns null if
-     * notification doesn't exist.
-     */
-    public NotificationEntry getPendingOrActiveNotif(String key) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        NotificationEntry entry = mPendingNotifications.get(key);
-        if (entry != null) {
-            return entry;
-        }
-        return mActiveNotifications.get(key);
-    }
-
-    private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
-        NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
-        if (activeExtender != null && activeExtender != extender) {
-            activeExtender.setShouldManageLifetime(entry, false);
-        }
-        mRetainedNotifications.put(entry, extender);
-        extender.setShouldManageLifetime(entry, true);
-    }
-
-    private void cancelLifetimeExtension(NotificationEntry entry) {
-        NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
-        if (activeExtender != null) {
-            activeExtender.setShouldManageLifetime(entry, false);
-        }
-    }
-
     /*
      * -----
      * Annexed from NotificationData below:
@@ -865,59 +105,11 @@
      * we'll try to keep the behavior the same and can simplify these interfaces in another pass
      */
 
-    /** Internalization of NotificationData#remove */
-    private void removeVisibleNotification(String key) {
-        // no need to synchronize if we're on the main thread dawg
-        Assert.isMainThread();
-
-        NotificationEntry removed = mActiveNotifications.remove(key);
-
-        if (removed == null) return;
-        mGroupManager.onEntryRemoved(removed);
-    }
-
-    /** @return list of active notifications filtered for the current user */
-    public List<NotificationEntry> getActiveNotificationsForCurrentUser() {
-        Trace.beginSection("NotificationEntryManager.getActiveNotificationsForCurrentUser");
-        Assert.isMainThread();
-        ArrayList<NotificationEntry> filtered = new ArrayList<>();
-
-        final int len = mActiveNotifications.size();
-        for (int i = 0; i < len; i++) {
-            NotificationEntry entry = mActiveNotifications.valueAt(i);
-            if (!mRanker.isNotificationForCurrentProfiles(entry)) {
-                continue;
-            }
-            filtered.add(entry);
-        }
-        Trace.endSection();
-        return filtered;
-    }
-
-    //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking
-    /**
-     * @param rankingMap the {@link RankingMap} to apply to the current notification list
-     * @param reason the reason for calling this method, which will be logged
-     */
-    public void updateRanking(RankingMap rankingMap, String reason) {
-        Trace.beginSection("NotificationEntryManager.updateRanking");
-        updateRankingAndSort(rankingMap, reason);
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingApplied();
-        }
-        Trace.endSection();
-    }
-
     /** Resorts / filters the current notification set with the current RankingMap */
     public void reapplyFilterAndSort(String reason) {
         mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
     }
 
-    /** Calls to NotificationRankingManager and updates mSortedAndFiltered */
-    private void updateRankingAndSort(RankingMap rankingMap, String reason) {
-        mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
-    }
-
     /** dump the current active notification list. Called from CentralSurfaces */
     public void dump(PrintWriter pw, String indent) {
         pw.println("NotificationEntryManager (Legacy)");
@@ -955,55 +147,10 @@
         pw.println("      notification=" + n.getNotification());
     }
 
-    /**
-     * This is the answer to the question "what notifications should the user be seeing right now?"
-     * These are sorted and filtered, and directly inform the notification shade what to show
-     *
-     * @return A read-only list of the currently active notifications
-     */
-    public List<NotificationEntry> getVisibleNotifications() {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mReadOnlyNotifications;
-    }
-
-    /**
-     * Returns a collections containing ALL notifications we know about, including ones that are
-     * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
-     */
-    @NonNull
-    @Override
-    public Collection<NotificationEntry> getAllNotifs() {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mReadOnlyAllNotifications;
-    }
-
-    @Nullable
-    @Override
-    public NotificationEntry getEntry(@NonNull String key) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return getPendingOrActiveNotif(key);
-    }
-
-    /** @return A count of the active notifications */
-    public int getActiveNotificationsCount() {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mReadOnlyNotifications.size();
-    }
-
-    /**
-     * @return {@code true} if there is at least one notification that should be visible right now
-     */
-    public boolean hasActiveNotifications() {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        return mReadOnlyNotifications.size() != 0;
-    }
-
-    @Override
     public void addCollectionListener(@NonNull NotifCollectionListener listener) {
         mNotifCollectionListeners.add(listener);
     }
 
-    @Override
     public void removeCollectionListener(@NonNull NotifCollectionListener listener) {
         mNotifCollectionListeners.remove(listener);
     }
@@ -1024,9 +171,6 @@
         boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
     }
 
-    private static final String TAG = "NotificationEntryMgr";
-    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
-
     /**
      * Used when a notification is removed and it doesn't have a reason that maps to one of the
      * reasons defined in NotificationListenerService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
deleted file mode 100644
index 54f1380..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
-
-import android.Manifest;
-import android.app.AppGlobals;
-import android.app.Notification;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
-
-import javax.inject.Inject;
-
-/** Component which manages the various reasons a notification might be filtered out.*/
-// TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
-//  Notification filtering is taken care of across the different Coordinators (mostly
-//  KeyguardCoordinator.java)
-@SysUISingleton
-public class NotificationFilter {
-
-    private final DebugModeFilterProvider mDebugNotificationFilter;
-    private final StatusBarStateController mStatusBarStateController;
-    private final KeyguardEnvironment mKeyguardEnvironment;
-    private final ForegroundServiceController mForegroundServiceController;
-    private final NotificationLockscreenUserManager mUserManager;
-    private final Boolean mIsMediaFlagEnabled;
-
-    @Inject
-    public NotificationFilter(
-            DebugModeFilterProvider debugNotificationFilter,
-            StatusBarStateController statusBarStateController,
-            KeyguardEnvironment keyguardEnvironment,
-            ForegroundServiceController foregroundServiceController,
-            NotificationLockscreenUserManager userManager,
-            MediaFeatureFlag mediaFeatureFlag) {
-        mDebugNotificationFilter = debugNotificationFilter;
-        mStatusBarStateController = statusBarStateController;
-        mKeyguardEnvironment = keyguardEnvironment;
-        mForegroundServiceController = foregroundServiceController;
-        mUserManager = userManager;
-        mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled();
-    }
-
-    /**
-     * @return true if the provided notification should NOT be shown right now.
-     */
-    public boolean shouldFilterOut(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        if (mDebugNotificationFilter.shouldFilterOut(entry)) {
-            return true;
-        }
-
-        if (!(mKeyguardEnvironment.isDeviceProvisioned()
-                || showNotificationEvenIfUnprovisioned(sbn))) {
-            return true;
-        }
-
-        if (!mKeyguardEnvironment.isNotificationForCurrentProfiles(sbn)) {
-            return true;
-        }
-
-        if (mUserManager.isLockscreenPublicMode(sbn.getUserId())
-                && (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
-                        || mUserManager.shouldHideNotifications(sbn.getUserId())
-                        || mUserManager.shouldHideNotifications(sbn.getKey()))) {
-            return true;
-        }
-
-        if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
-            return true;
-        }
-
-        if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) {
-            return true;
-        }
-
-        if (entry.getRanking().isSuspended()) {
-            return true;
-        }
-
-        if (mForegroundServiceController.isDisclosureNotification(sbn)
-                && !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) {
-            // this is a foreground-service disclosure for a user that does not need to show one
-            return true;
-        }
-
-        if (mIsMediaFlagEnabled && isMediaNotification(sbn)) {
-            return true;
-        }
-        return false;
-    }
-
-    // Q: What kinds of notifications should show during setup?
-    // A: Almost none! Only things coming from packages with permission
-    // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
-    // as relevant for setup (see below).
-    private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
-        return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
-    }
-
-    @VisibleForTesting
-    static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
-            StatusBarNotification sbn) {
-        return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
-                sbn.getUid()) == PackageManager.PERMISSION_GRANTED
-                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
-    }
-
-    private static int checkUidPermission(IPackageManager packageManager, String permission,
-            int uid) {
-        try {
-            return packageManager.checkUidPermission(permission, uid);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index b6392f7..585d871 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -61,11 +61,11 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.icon.IconPack;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
@@ -421,7 +421,7 @@
      * Get the children that are actually attached to this notification's row.
      *
      * TODO: Seems like most callers here should probably be using
-     * {@link NotificationGroupManagerLegacy#getChildren}
+     * {@link GroupMembershipManager#getChildren(ListEntry)}
      */
     public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
         if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
deleted file mode 100644
index a92cff8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ /dev/null
@@ -1,227 +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.collection
-
-import android.app.Notification
-import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_MIN
-import android.service.notification.NotificationListenerService.Ranking
-import android.service.notification.NotificationListenerService.RankingMap
-import android.service.notification.StatusBarNotification
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
-import com.android.systemui.statusbar.notification.NotificationFilter
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.PriorityBucket
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import dagger.Lazy
-import java.util.Objects
-import javax.inject.Inject
-
-private const val TAG = "NotifRankingManager"
-
-/**
- * NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and
- * updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters
- * a set of entries (but retains none of them). We also set buckets on the entries here since
- * bucketing is tied closely to sorting.
- *
- * For the curious: this class is one iteration closer to null of what used to be called
- * NotificationData.java.
- */
-open class NotificationRankingManager @Inject constructor(
-    private val mediaManagerLazy: Lazy<NotificationMediaManager>,
-    private val groupManager: NotificationGroupManagerLegacy,
-    private val headsUpManager: HeadsUpManager,
-    private val notifFilter: NotificationFilter,
-    private val logger: NotificationEntryManagerLogger,
-    private val sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
-    private val highPriorityProvider: HighPriorityProvider,
-    private val keyguardEnvironment: KeyguardEnvironment
-) : LegacyNotificationRanker {
-
-    override var rankingMap: RankingMap? = null
-        protected set
-    private val mediaManager by lazy {
-        mediaManagerLazy.get()
-    }
-    private val usePeopleFiltering: Boolean
-        get() = sectionsFeatureManager.isFilteringEnabled()
-    private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b ->
-        val na = a.sbn
-        val nb = b.sbn
-        val aRank = a.ranking.rank
-        val bRank = b.ranking.rank
-
-        val aIsFsn = a.isColorizedForegroundService()
-        val bIsFsn = b.isColorizedForegroundService()
-
-        val aCall = a.isImportantCall()
-        val bCall = b.isImportantCall()
-
-        val aPersonType = a.getPeopleNotificationType()
-        val bPersonType = b.getPeopleNotificationType()
-
-        val aMedia = a.isImportantMedia()
-        val bMedia = b.isImportantMedia()
-
-        val aSystemMax = a.isSystemMax()
-        val bSystemMax = b.isSystemMax()
-
-        val aHeadsUp = a.isRowHeadsUp
-        val bHeadsUp = b.isRowHeadsUp
-
-        val aIsHighPriority = a.isHighPriority()
-        val bIsHighPriority = b.isHighPriority()
-        when {
-            aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
-            // Provide consistent ranking with headsUpManager
-            aHeadsUp -> headsUpManager.compare(a, b)
-            aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1
-            aCall != bCall -> if (aCall) -1 else 1
-            usePeopleFiltering && aPersonType != bPersonType ->
-                peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
-            // Upsort current media notification.
-            aMedia != bMedia -> if (aMedia) -1 else 1
-            // Upsort PRIORITY_MAX system notifications
-            aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
-            aIsHighPriority != bIsHighPriority ->
-                -1 * aIsHighPriority.compareTo(bIsHighPriority)
-            aRank != bRank -> aRank - bRank
-            else -> nb.notification.`when`.compareTo(na.notification.`when`)
-        }
-    }
-
-    override fun updateRanking(
-        newRankingMap: RankingMap?,
-        entries: Collection<NotificationEntry>,
-        reason: String
-    ): List<NotificationEntry> {
-        // TODO: may not be ideal to guard on null here, but this code is implementing exactly what
-        // NotificationData used to do
-        if (newRankingMap != null) {
-            rankingMap = newRankingMap
-            updateRankingForEntries(entries)
-        }
-        return synchronized(this) {
-            filterAndSortLocked(entries, reason)
-        }
-    }
-
-    override fun isNotificationForCurrentProfiles(
-        entry: NotificationEntry
-    ): Boolean {
-        return keyguardEnvironment.isNotificationForCurrentProfiles(entry.sbn)
-    }
-
-    /** Uses the [rankingComparator] to sort notifications which aren't filtered */
-    private fun filterAndSortLocked(
-        entries: Collection<NotificationEntry>,
-        reason: String
-    ): List<NotificationEntry> {
-        logger.logFilterAndSort(reason)
-        val filtered = entries.asSequence()
-                .filterNot(this::filter)
-                .sortedWith(rankingComparator)
-                .toList()
-        entries.forEach { it.bucket = getBucketForEntry(it) }
-        return filtered
-    }
-
-    private fun filter(entry: NotificationEntry): Boolean {
-        val filtered = notifFilter.shouldFilterOut(entry)
-        if (filtered) {
-            // notification is removed from the list, so we reset its initialization time
-            entry.resetInitializationTime()
-        }
-        return filtered
-    }
-
-    @PriorityBucket
-    private fun getBucketForEntry(entry: NotificationEntry): Int {
-        val isImportantCall = entry.isImportantCall()
-        val isHeadsUp = entry.isRowHeadsUp
-        val isMedia = entry.isImportantMedia()
-        val isSystemMax = entry.isSystemMax()
-        return when {
-            entry.isColorizedForegroundService() || isImportantCall -> BUCKET_FOREGROUND_SERVICE
-            usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE
-            isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING
-            else -> BUCKET_SILENT
-        }
-    }
-
-    private fun updateRankingForEntries(entries: Iterable<NotificationEntry>) {
-        rankingMap?.let { rankingMap ->
-            synchronized(entries) {
-                for (entry in entries) {
-                    val newRanking = Ranking()
-                    if (!rankingMap.getRanking(entry.key, newRanking)) {
-                        continue
-                    }
-                    entry.ranking = newRanking
-
-                    val newOverrideGroupKey = newRanking.overrideGroupKey
-                    if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
-                        val oldGroupKey = entry.sbn.groupKey
-                        val oldIsGroup = entry.sbn.isGroup
-                        val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
-                        entry.sbn.overrideGroupKey = newOverrideGroupKey
-                        groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
-                                oldIsGroupSummary)
-                    }
-                }
-            }
-        }
-    }
-
-    private fun NotificationEntry.isImportantMedia() =
-            key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
-
-    private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
-
-    private fun NotificationEntry.getPeopleNotificationType() =
-            peopleNotificationIdentifier.getPeopleNotificationType(this)
-
-    private fun NotificationEntry.isHighPriority() =
-            highPriorityProvider.isHighPriority(this)
-}
-
-// Convenience functions
-private fun NotificationEntry.isSystemMax() =
-        importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
-
-private fun StatusBarNotification.isSystemNotification() =
-        "android" == packageName || "com.android.systemui" == packageName
-
-private fun NotificationEntry.isImportantCall() =
-        sbn.notification.isStyle(Notification.CallStyle::class.java) && importance > IMPORTANCE_MIN
-
-private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run {
-    isForegroundService && isColorized && importance > IMPORTANCE_MIN
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ef1e57b..6e76691 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -286,7 +286,7 @@
                 if (isInflated(child)) {
                     // TODO: May want to put an animation hint here so view manager knows to treat
                     //  this differently from a regular removal animation
-                    freeNotifViews(child);
+                    freeNotifViews(child, "Past last visible group child");
                 }
             }
         }
@@ -379,7 +379,8 @@
         mNotifInflatingFilter.invalidateList("onInflationFinished for " + logKey(entry));
     }
 
-    private void freeNotifViews(NotificationEntry entry) {
+    private void freeNotifViews(NotificationEntry entry, String reason) {
+        mLogger.logFreeNotifViews(entry, reason);
         mViewBarn.removeViewForEntry(entry);
         mNotifInflater.releaseViews(entry);
         // TODO: clear the entry's row here, or even better, stop setting the row on the entry!
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 30f1315..c4f4ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -31,7 +31,7 @@
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = entry.logKey
         }, {
-            "NOTIF INFLATED $str1"
+            "Inflation completed for notif $str1"
         })
     }
 
@@ -40,7 +40,16 @@
             str1 = entry.logKey
             str2 = reason
         }, {
-            "NOTIF INFLATION ABORTED $str1 reason=$str2"
+            "Infation aborted for notif $str1 reason=$str2"
+        })
+    }
+
+    fun logFreeNotifViews(entry: NotificationEntry, reason: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "Freeing content views for notif $str1 reason=$str2"
         })
     }
 
@@ -70,4 +79,4 @@
     }
 }
 
-private const val TAG = "PreparationCoordinator"
\ No newline at end of file
+private const val TAG = "PreparationCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
index 9d5b859..496266f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.notification.collection.inflation
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.NotificationEntryListener
-import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener
 import javax.inject.Inject
@@ -31,12 +29,4 @@
     /** Emit the [Listener.onViewBound] event to all registered listeners. */
     fun notifyViewBound(entry: NotificationEntry) =
         listeners.forEach { listener -> listener.onViewBound(entry) }
-
-    /** Initialize this for the legacy pipeline. */
-    fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) {
-        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
-            override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry)
-            override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry)
-        })
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt
deleted file mode 100644
index 49bc48e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy
-
-import android.service.notification.NotificationListenerService
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-
-interface LegacyNotificationRanker {
-    val rankingMap: NotificationListenerService.RankingMap?
-
-    fun updateRanking(
-        newRankingMap: NotificationListenerService.RankingMap?,
-        entries: Collection<NotificationEntry>,
-        reason: String
-    ): List<NotificationEntry>
-
-    fun isNotificationForCurrentProfiles(
-        entry: NotificationEntry
-    ): Boolean
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java
deleted file mode 100644
index 12353f8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Stub implementation that we use until we get passed the "real" one in the form of
- * {@link com.android.systemui.statusbar.notification.collection.NotificationRankingManager}
- */
-public class LegacyNotificationRankerStub implements LegacyNotificationRanker {
-    private RankingMap mRankingMap = new RankingMap(new Ranking[] {});
-
-    @NonNull
-    @Override
-    public List<NotificationEntry> updateRanking(
-            @Nullable RankingMap newRankingMap,
-            @NonNull Collection<NotificationEntry> entries,
-            @NonNull String reason) {
-        if (newRankingMap != null) {
-            mRankingMap = newRankingMap;
-        }
-        List<NotificationEntry> ranked = new ArrayList<>(entries);
-        ranked.sort(mEntryComparator);
-        return ranked;
-    }
-
-    @Nullable
-    @Override
-    public RankingMap getRankingMap() {
-        return mRankingMap;
-    }
-
-    private final Comparator<NotificationEntry> mEntryComparator = Comparator.comparingLong(
-            o -> o.getSbn().getNotification().when);
-
-    @Override
-    public boolean isNotificationForCurrentProfiles(@NonNull NotificationEntry entry) {
-        return true;
-    }
-}
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
index ae4f2bb..89445a5 100644
--- 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
@@ -19,9 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 
 import javax.inject.Inject;
 
@@ -32,44 +29,17 @@
 @SysUISingleton
 public class LowPriorityInflationHelper {
     private final NotificationGroupManagerLegacy mGroupManager;
-    private final RowContentBindStage mRowContentBindStage;
     private final NotifPipelineFlags mNotifPipelineFlags;
 
     @Inject
     LowPriorityInflationHelper(
             NotificationGroupManagerLegacy groupManager,
-            RowContentBindStage rowContentBindStage,
             NotifPipelineFlags notifPipelineFlags) {
         mGroupManager = groupManager;
-        mRowContentBindStage = rowContentBindStage;
         mNotifPipelineFlags = notifPipelineFlags;
     }
 
     /**
-     * Check if we inflated the wrong version of the view and if we need to reinflate the
-     * content views to be their low priority version or not.
-     *
-     * Whether we inflate the low priority view or not depends on the notification being visually
-     * part of a group. Since group membership is determined AFTER inflation, we're forced to check
-     * again at a later point in the pipeline to see if we inflated the wrong view and reinflate
-     * the correct one here.
-     *
-     * TODO: The group manager should run before inflation so that we don't deal with this
-     */
-    public void recheckLowPriorityViewAndInflate(
-            NotificationEntry entry,
-            ExpandableNotificationRow row) {
-        mNotifPipelineFlags.checkLegacyPipelineEnabled();
-        RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
-        final boolean shouldBeLowPriority = shouldUseLowPriorityView(entry);
-        if (!row.isRemoved() && row.isLowPriority() != shouldBeLowPriority) {
-            params.setUseLowPriority(shouldBeLowPriority);
-            mRowContentBindStage.requestRebind(entry,
-                    en -> row.setIsLowPriority(shouldBeLowPriority));
-        }
-    }
-
-    /**
      * Whether the notification should inflate a low priority version of its content views.
      */
     public boolean shouldUseLowPriorityView(NotificationEntry 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
index b8da9a8..d41f6fe 100644
--- 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
@@ -33,13 +33,9 @@
 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.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.Compile;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -47,7 +43,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -65,12 +60,7 @@
  * 2. Tracking group expansion states
  */
 @SysUISingleton
-public class NotificationGroupManagerLegacy implements
-        OnHeadsUpChangedListener,
-        StateListener,
-        GroupMembershipManager,
-        GroupExpansionManager,
-        Dumpable {
+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);
@@ -81,14 +71,10 @@
      */
     private static final long POST_BATCH_MAX_AGE = 5000;
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
-    private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
-            new ArraySet<>();
     private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
     private final Optional<Bubbles> mBubblesOptional;
     private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get);
-    private int mBarState = -1;
-    private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
-    private HeadsUpManager mHeadsUpManager;
+    private final HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private boolean mIsUpdatingUnchangedGroup;
 
     @Inject
@@ -111,47 +97,8 @@
         mEventDispatcher.registerGroupChangeListener(listener);
     }
 
-    @Override
-    public void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener) {
-        mExpansionChangeListeners.add(listener);
-    }
-
-    @Override
-    public boolean isGroupExpanded(NotificationEntry entry) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
-        if (group == null) {
-            return false;
-        }
-        return group.expanded;
-    }
-
-    /**
-     * @return if the group that this notification is associated with logically is expanded
-     */
-    public boolean isLogicalGroupExpanded(StatusBarNotification sbn) {
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
-        if (group == null) {
-            return false;
-        }
-        return group.expanded;
-    }
-
-    @Override
-    public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
-        if (group == null) {
-            return;
-        }
-        setGroupExpanded(group, expanded);
-    }
-
     private void setGroupExpanded(NotificationGroup group, boolean expanded) {
         group.expanded = expanded;
-        if (group.summary != null) {
-            for (OnGroupExpansionChangeListener listener : mExpansionChangeListeners) {
-                listener.onGroupExpansionChange(group.summary.getRow(), expanded);
-            }
-        }
     }
 
     /**
@@ -212,19 +159,6 @@
         }
     }
 
-    /**
-     * Notify the group manager that a new entry was added
-     */
-    public void onEntryAdded(final NotificationEntry added) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryAdded: entry=" + logKey(added));
-        }
-        mEventDispatcher.openBufferScope();
-        updateIsolation(added);
-        onEntryAddedInternal(added);
-        mEventDispatcher.closeBufferScope();
-    }
-
     private void onEntryAddedInternal(final NotificationEntry added) {
         if (added.isRowRemoved()) {
             added.setDebugThrowable(new Throwable());
@@ -499,106 +433,13 @@
         return result;
     }
 
-    /**
-     * Update an entry's group information
-     * @param entry notification entry to update
-     * @param oldNotification previous notification info before this update
-     */
-    public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryUpdated: entry=" + logKey(entry));
-        }
-        onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
-                oldNotification.getNotification().isGroupSummary());
-    }
-
-    /**
-     * Updates an entry's group information
-     * @param entry notification entry to update
-     * @param oldGroupKey the notification's previous group key before this update
-     * @param oldIsGroup whether this notification was a group before this update
-     * @param oldIsGroupSummary whether this notification was a group summary before this update
-     */
-    public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
-            boolean oldIsGroupSummary) {
-        String newGroupKey = entry.getSbn().getGroupKey();
-        boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
-        boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
-        boolean isGroupChild = isGroupChild(entry.getSbn());
-        mEventDispatcher.openBufferScope();
-        mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
-        if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
-            onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
-        }
-        onEntryAddedInternal(entry);
-        mIsUpdatingUnchangedGroup = false;
-        if (isIsolated(entry.getSbn().getKey())) {
-            mIsolatedEntries.put(entry.getKey(), entry.getSbn());
-            if (groupKeysChanged) {
-                updateSuppression(mGroupMap.get(oldGroupKey));
-            }
-            // Always update the suppression of the group from which you're isolated, in case
-            // this entry was or now is the alertOverride for that group.
-            updateSuppression(mGroupMap.get(newGroupKey));
-        } else if (!wasGroupChild && isGroupChild) {
-            onEntryBecomingChild(entry);
-        }
-        mEventDispatcher.closeBufferScope();
-    }
-
-    /**
-     * Whether the given notification is the summary of a group that is being suppressed
-     */
-    public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
-        return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
-    }
-
-    /**
-     * If the given notification is a summary, get the group for it.
-     */
-    public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
-        if (sbn.getNotification().isGroupSummary()) {
-            return mGroupMap.get(getGroupKey(sbn));
-        }
-        return null;
-    }
-
-    private boolean isOnlyChild(StatusBarNotification sbn) {
-        return !sbn.getNotification().isGroupSummary()
-                && getTotalNumberOfChildren(sbn) == 1;
-    }
-
-    @Override
-    public boolean isOnlyChildInGroup(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        if (!isOnlyChild(sbn)) {
-            return false;
-        }
-        NotificationEntry logicalGroupSummary = getLogicalGroupSummary(entry);
-        return logicalGroupSummary != null && !logicalGroupSummary.getSbn().equals(sbn);
-    }
-
-    private int getTotalNumberOfChildren(StatusBarNotification sbn) {
-        int isolatedChildren = getNumberOfIsolatedChildren(sbn.getGroupKey());
-        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
-        int realChildren = group != null ? group.children.size() : 0;
-        return isolatedChildren + realChildren;
-    }
-
-    private boolean isGroupSuppressed(String groupKey) {
-        NotificationGroup group = mGroupMap.get(groupKey);
-        return group != null && group.suppressed;
-    }
-
     private void setStatusBarState(int newState) {
-        mBarState = newState;
-        if (mBarState == StatusBarState.KEYGUARD) {
+        if (newState == StatusBarState.KEYGUARD) {
             collapseGroups();
         }
     }
 
-    @Override
-    public void 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());
@@ -612,7 +453,6 @@
         }
     }
 
-    @Override
     public boolean isChildInGroup(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
         if (!isGroupChild(sbn)) {
@@ -631,67 +471,6 @@
         return true;
     }
 
-    @Override
-    public boolean isGroupSummary(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        if (!isGroupSummary(sbn)) {
-            return false;
-        }
-        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
-        if (group == null || group.summary == null) {
-            return false;
-        }
-        return !group.children.isEmpty() && Objects.equals(group.summary.getSbn(), sbn);
-    }
-
-    @Override
-    public NotificationEntry getGroupSummary(NotificationEntry entry) {
-        return getGroupSummary(getGroupKey(entry.getSbn()));
-    }
-
-    @Override
-    public NotificationEntry getLogicalGroupSummary(NotificationEntry entry) {
-        return getGroupSummary(entry.getSbn().getGroupKey());
-    }
-
-    @Nullable
-    private NotificationEntry getGroupSummary(String groupKey) {
-        NotificationGroup group = mGroupMap.get(groupKey);
-        //TODO: see if this can become an Entry
-        return group == null ? null
-                : group.summary;
-    }
-
-    /**
-     * Get the children that are logically in the summary's group, whether or not they are isolated.
-     *
-     * @param summary summary of a group
-     * @return list of the children
-     */
-    public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) {
-        NotificationGroup group = mGroupMap.get(summary.getGroupKey());
-        if (group == null) {
-            return null;
-        }
-        ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
-        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(summary.getGroupKey())) {
-                children.add(mGroupMap.get(sbn.getKey()).summary);
-            }
-        }
-        return children;
-    }
-
-    @Override
-    public @Nullable List<NotificationEntry> getChildren(ListEntry listEntrySummary) {
-        NotificationEntry summary = listEntrySummary.getRepresentativeEntry();
-        NotificationGroup group = mGroupMap.get(summary.getSbn().getGroupKey());
-        if (group == null) {
-            return null;
-        }
-        return new ArrayList<>(group.children.values());
-    }
-
     /**
      * If there is a {@link NotificationGroup} associated with the provided entry, this method
      * will update the suppression of that group.
@@ -710,7 +489,7 @@
      * @param sbn notification to check
      * @return the key of the notification
      */
-    public String getGroupKey(StatusBarNotification sbn) {
+    private String getGroupKey(StatusBarNotification sbn) {
         return getGroupKey(sbn.getKey(), sbn.getGroupKey());
     }
 
@@ -721,37 +500,17 @@
         return groupKey;
     }
 
-    @Override
-    public boolean toggleGroupExpansion(NotificationEntry entry) {
-        NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
-        if (group == null) {
-            return false;
-        }
-        setGroupExpanded(group, !group.expanded);
-        return group.expanded;
-    }
-
     private boolean isIsolated(String sbnKey) {
         return mIsolatedEntries.containsKey(sbnKey);
     }
 
     /**
-     * Is this notification the summary of a group?
-     */
-    public boolean isGroupSummary(StatusBarNotification sbn) {
-        if (isIsolated(sbn.getKey())) {
-            return true;
-        }
-        return sbn.getNotification().isGroupSummary();
-    }
-
-    /**
      * Whether a notification is visually a group child.
      *
      * @param sbn notification to check
      * @return true if it is visually a group child
      */
-    public boolean isGroupChild(StatusBarNotification sbn) {
+    private boolean isGroupChild(StatusBarNotification sbn) {
         return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
     }
 
@@ -762,11 +521,6 @@
         return isGroup && !isGroupSummary;
     }
 
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        updateIsolation(entry);
-    }
-
     /**
      * 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
@@ -783,9 +537,6 @@
         if (isImportantConversation(entry)) {
             return true;
         }
-        if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
-            return false;
-        }
         NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
         return (sbn.getNotification().fullScreenIntent != null
                     || notificationGroup == null
@@ -825,7 +576,7 @@
     /**
      * Update the isolation of an entry, splitting it from the group.
      */
-    public void updateIsolation(NotificationEntry entry) {
+    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
@@ -866,13 +617,6 @@
                 || notificationGroup.summary.isGroupNotFullyVisible();
     }
 
-    /**
-     * Directly set the heads up manager to avoid circular dependencies
-     */
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("GroupManagerLegacy state:");
@@ -893,7 +637,7 @@
     }
 
     /** Get the group key, reformatted for logging, for the (optional) group */
-    public static String logGroupKey(NotificationGroup group) {
+    private static String logGroupKey(NotificationGroup group) {
         if (group == null) {
             return "null";
         }
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
index 456bf51..bb8c0e0 100644
--- 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
@@ -16,84 +16,32 @@
 
 package com.android.systemui.statusbar.notification.collection.legacy;
 
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.View;
-
-import androidx.collection.ArraySet;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * 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 implements OnHeadsUpChangedListener, Dumpable {
+public class VisualStabilityManager {
 
-    private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
-
-    private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
-    private final ArraySet<Callback> mPersistentReorderingCallbacks = new ArraySet<>();
-    private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
-    private final ArraySet<Callback> mPersistentGroupCallbacks = new ArraySet<>();
-    private final Handler mHandler;
     private final VisualStabilityProvider mVisualStabilityProvider;
 
     private boolean mPanelExpanded;
     private boolean mScreenOn;
-    private boolean mReorderingAllowed;
-    private boolean mGroupChangedAllowed;
-    private boolean mIsTemporaryReorderingAllowed;
-    private long mTemporaryReorderingStart;
-    private VisibilityLocationProvider mVisibilityLocationProvider;
-    private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
-    private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
-    private ArraySet<View> mAddedChildren = new ArraySet<>();
     private boolean mPulsing;
 
     /**
      * Injected constructor. See {@link NotificationsModule}.
      */
     public VisualStabilityManager(
-            NotificationEntryManager notificationEntryManager,
             VisualStabilityProvider visualStabilityProvider,
-            @Main Handler handler,
             StatusBarStateController statusBarStateController,
-            WakefulnessLifecycle wakefulnessLifecycle,
-            DumpManager dumpManager) {
+            WakefulnessLifecycle wakefulnessLifecycle) {
 
         mVisualStabilityProvider = visualStabilityProvider;
-        mHandler = handler;
-        dumpManager.registerDumpable(this);
-
-        if (notificationEntryManager != null) {
-            notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-                @Override
-                public void onPreEntryUpdated(NotificationEntry entry) {
-                    final boolean ambientStateHasChanged =
-                            entry.isAmbient() != entry.getRow().isLowPriority();
-                    if (ambientStateHasChanged) {
-                        // note: entries are removed in onReorderingFinished
-                        mLowPriorityReorderingViews.add(entry);
-                    }
-                }
-            });
-        }
 
         if (statusBarStateController != null) {
             setPulsing(statusBarStateController.isPulsing());
@@ -116,40 +64,6 @@
     }
 
     /**
-     * Add a callback to invoke when reordering is allowed again.
-     *
-     * @param callback the callback to add
-     * @param persistent {@code true} if this callback should this callback be persisted, otherwise
-     *                               it will be removed after a single invocation
-     */
-    public void addReorderingAllowedCallback(Callback callback, boolean persistent) {
-        if (persistent) {
-            mPersistentReorderingCallbacks.add(callback);
-        }
-        if (mReorderingAllowedCallbacks.contains(callback)) {
-            return;
-        }
-        mReorderingAllowedCallbacks.add(callback);
-    }
-
-    /**
-     * Add a callback to invoke when group changes are allowed again.
-     *
-     * @param callback the callback to add
-     * @param persistent {@code true} if this callback should this callback be persisted, otherwise
-     *                               it will be removed after a single invocation
-     */
-    public void addGroupChangesAllowedCallback(Callback callback, boolean persistent) {
-        if (persistent) {
-            mPersistentGroupCallbacks.add(callback);
-        }
-        if (mGroupChangesAllowedCallbacks.contains(callback)) {
-            return;
-        }
-        mGroupChangesAllowedCallbacks.add(callback);
-    }
-
-    /**
      * @param screenOn whether the screen is on
      */
     private void setScreenOn(boolean screenOn) {
@@ -177,133 +91,8 @@
     }
 
     private void updateAllowedStates() {
-        boolean reorderingAllowed =
-                (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
-        boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
-        mReorderingAllowed = reorderingAllowed;
-        if (changedToTrue) {
-            notifyChangeAllowed(mReorderingAllowedCallbacks, mPersistentReorderingCallbacks);
-        }
+        boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
         mVisualStabilityProvider.setReorderingAllowed(reorderingAllowed);
-        boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
-        changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
-        mGroupChangedAllowed = groupChangesAllowed;
-        if (changedToTrue) {
-            notifyChangeAllowed(mGroupChangesAllowedCallbacks, mPersistentGroupCallbacks);
-        }
-    }
-
-    private void notifyChangeAllowed(ArrayList<Callback> callbacks,
-            ArraySet<Callback> persistentCallbacks) {
-        for (int i = 0; i < callbacks.size(); i++) {
-            Callback callback = callbacks.get(i);
-            callback.onChangeAllowed();
-            if (!persistentCallbacks.contains(callback)) {
-                callbacks.remove(callback);
-                i--;
-            }
-        }
-    }
-
-    /**
-     * @return whether reordering is currently allowed in general.
-     */
-    public boolean isReorderingAllowed() {
-        return mReorderingAllowed;
-    }
-
-    /**
-     * @return whether changes in the grouping should be allowed right now.
-     */
-    public boolean areGroupChangesAllowed() {
-        return mGroupChangedAllowed;
-    }
-
-    /**
-     * @return whether a specific notification is allowed to reorder. Certain notifications are
-     * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
-     * notifications or heads-up notifications that are out of view.
-     */
-    public boolean canReorderNotification(ExpandableNotificationRow row) {
-        if (mReorderingAllowed) {
-            return true;
-        }
-        if (mAddedChildren.contains(row)) {
-            return true;
-        }
-        if (mLowPriorityReorderingViews.contains(row.getEntry())) {
-            return true;
-        }
-        if (mAllowedReorderViews.contains(row)
-                && !mVisibilityLocationProvider.isInVisibleLocation(row.getEntry())) {
-            return true;
-        }
-        return false;
-    }
-
-    public void setVisibilityLocationProvider(
-            VisibilityLocationProvider visibilityLocationProvider) {
-        mVisibilityLocationProvider = visibilityLocationProvider;
-    }
-
-    /**
-     * Notifications have been reordered, so reset all the allowed list of views that are allowed
-     * to reorder.
-     */
-    public void onReorderingFinished() {
-        mAllowedReorderViews.clear();
-        mAddedChildren.clear();
-        mLowPriorityReorderingViews.clear();
-    }
-
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        if (isHeadsUp) {
-            // Heads up notifications should in general be allowed to reorder if they are out of
-            // view and stay at the current location if they aren't.
-            mAllowedReorderViews.add(entry.getRow());
-        }
-    }
-
-    /**
-     * Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
-     * to this method will extend the timer.
-     */
-    public void temporarilyAllowReordering() {
-        mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
-        mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
-        if (!mIsTemporaryReorderingAllowed) {
-            mTemporaryReorderingStart = SystemClock.elapsedRealtime();
-        }
-        mIsTemporaryReorderingAllowed = true;
-        updateAllowedStates();
-    }
-
-    private final Runnable mOnTemporaryReorderingExpired = () -> {
-        mIsTemporaryReorderingAllowed = false;
-        updateAllowedStates();
-    };
-
-    /**
-     * Notify the visual stability manager that a new view was added and should be allowed to
-     * reorder next time.
-     */
-    public void notifyViewAddition(View view) {
-        mAddedChildren.add(view);
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("VisualStabilityManager state:");
-        pw.print("  mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
-        pw.print("  mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
-
-        long now = SystemClock.elapsedRealtime();
-        pw.print("    Temporary reordering window has been open for ");
-        pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
-        pw.println("ms");
-
-        pw.println();
     }
 
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
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 fac234c..eda2eec 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
@@ -30,7 +30,6 @@
 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.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -53,7 +52,6 @@
 import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager;
 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.NotificationRowBinder;
 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;
@@ -123,22 +121,13 @@
             NotificationEntryManagerLogger logger,
             NotificationGroupManagerLegacy groupManager,
             NotifPipelineFlags notifPipelineFlags,
-            Lazy<NotificationRowBinder> notificationRowBinderLazy,
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
-            DumpManager dumpManager,
             @Background Executor bgExecutor) {
         return new NotificationEntryManager(
-                logger,
-                groupManager,
-                notifPipelineFlags,
-                notificationRowBinderLazy,
-                notificationRemoteInputManagerLazy,
-                leakDetector,
-                statusBarService,
-                dumpManager,
-                bgExecutor);
+                logger
+        );
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
@@ -162,8 +151,7 @@
             Optional<BubblesManager> bubblesManagerOptional,
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback,
-            ShadeController shadeController,
-            DumpManager dumpManager) {
+            ShadeController shadeController) {
         return new NotificationGutsManager(
                 context,
                 centralSurfacesOptionalLazy,
@@ -181,8 +169,8 @@
                 bubblesManagerOptional,
                 uiEventLogger,
                 onUserInteractionCallback,
-                shadeController,
-                dumpManager);
+                shadeController
+        );
     }
 
     /** Provides an instance of {@link NotifGutsViewManager} */
@@ -193,19 +181,14 @@
     @SysUISingleton
     @Provides
     static VisualStabilityManager provideVisualStabilityManager(
-            NotificationEntryManager notificationEntryManager,
             VisualStabilityProvider visualStabilityProvider,
-            @Main Handler handler,
             StatusBarStateController statusBarStateController,
-            WakefulnessLifecycle wakefulnessLifecycle,
-            DumpManager dumpManager) {
+            WakefulnessLifecycle wakefulnessLifecycle) {
         return new VisualStabilityManager(
-                notificationEntryManager,
                 visualStabilityProvider,
-                handler,
                 statusBarStateController,
-                wakefulnessLifecycle,
-                dumpManager);
+                wakefulnessLifecycle
+        );
     }
 
     /** Provides an instance of {@link NotificationLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
deleted file mode 100644
index 74fb3f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ /dev/null
@@ -1,185 +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.interruption;
-
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import javax.inject.Inject;
-
-/**
- * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
- * entry events and appropriately binds or unbinds the heads up view and promotes it to the top
- * of the screen.
- */
-@SysUISingleton
-public class HeadsUpController {
-    private final HeadsUpViewBinder mHeadsUpViewBinder;
-    private final NotificationInterruptStateProvider mInterruptStateProvider;
-    private final NotificationRemoteInputManager mRemoteInputManager;
-    private final VisualStabilityManager mVisualStabilityManager;
-    private final StatusBarStateController mStatusBarStateController;
-    private final NotificationListener mNotificationListener;
-    private final HeadsUpManager mHeadsUpManager;
-
-    @Inject
-    HeadsUpController(
-            HeadsUpViewBinder headsUpViewBinder,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
-            HeadsUpManager headsUpManager,
-            NotificationRemoteInputManager remoteInputManager,
-            StatusBarStateController statusBarStateController,
-            VisualStabilityManager visualStabilityManager,
-            NotificationListener notificationListener) {
-        mHeadsUpViewBinder = headsUpViewBinder;
-        mHeadsUpManager = headsUpManager;
-        mInterruptStateProvider = notificationInterruptStateProvider;
-        mRemoteInputManager = remoteInputManager;
-        mStatusBarStateController = statusBarStateController;
-        mVisualStabilityManager = visualStabilityManager;
-        mNotificationListener = notificationListener;
-    }
-
-    /**
-     * Attach this controller and add its listeners.
-     */
-    public void attach(
-            NotificationEntryManager entryManager,
-            HeadsUpManager headsUpManager) {
-        entryManager.addCollectionListener(mCollectionListener);
-        headsUpManager.addListener(mOnHeadsUpChangedListener);
-    }
-
-    private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
-        @Override
-        public void onEntryAdded(NotificationEntry entry) {
-            if (mInterruptStateProvider.shouldHeadsUp(entry)) {
-                mHeadsUpViewBinder.bindHeadsUpView(
-                        entry, HeadsUpController.this::showAlertingView);
-            }
-        }
-
-        @Override
-        public void onEntryUpdated(NotificationEntry entry) {
-            updateHunState(entry);
-        }
-
-        @Override
-        public void onEntryRemoved(NotificationEntry entry, int reason) {
-            stopAlerting(entry);
-        }
-
-        @Override
-        public void onEntryCleanUp(NotificationEntry entry) {
-            mHeadsUpViewBinder.abortBindCallback(entry);
-        }
-    };
-
-    /**
-     * Adds the entry to the HUN manager and show it for the first time.
-     */
-    private void showAlertingView(NotificationEntry entry) {
-        mHeadsUpManager.showNotification(entry);
-        if (!mStatusBarStateController.isDozing()) {
-            // Mark as seen immediately
-            setNotificationShown(entry.getSbn());
-        }
-    }
-
-    private void updateHunState(NotificationEntry entry) {
-        boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
-        // includes check for whether this notification should be filtered:
-        boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry);
-        final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
-        if (wasHeadsUp) {
-            if (shouldHeadsUp) {
-                mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
-            } else {
-                // We don't want this to be interrupting anymore, let's remove it
-                mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
-            }
-        } else if (shouldHeadsUp && hunAgain) {
-            mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification);
-        }
-    }
-
-    private void setNotificationShown(StatusBarNotification n) {
-        try {
-            mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
-        } catch (RuntimeException e) {
-            Log.d(TAG, "failed setNotificationsShown: ", e);
-        }
-    }
-
-    private void stopAlerting(NotificationEntry entry) {
-        // Attempt to remove notifications from their HUN manager.
-        // Though the remove itself may fail, it lets the manager know to remove as soon as
-        // possible.
-        String key = entry.getKey();
-        if (mHeadsUpManager.isAlerting(key)) {
-            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
-            // sending look longer than it takes.
-            // Also we should not defer the removal if reordering isn't allowed since otherwise
-            // some notifications can't disappear before the panel is closed.
-            boolean ignoreEarliestRemovalTime =
-                    mRemoteInputManager.isSpinning(key)
-                            && !FORCE_REMOTE_INPUT_HISTORY
-                            || !mVisualStabilityManager.isReorderingAllowed();
-            mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
-        }
-    }
-
-    /**
-     * Checks whether an update for a notification warrants an alert for the user.
-     *
-     * @param oldEntry the entry for this notification.
-     * @param newNotification the new notification for this entry.
-     * @return whether this notification should alert the user.
-     */
-    public static boolean alertAgain(
-            NotificationEntry oldEntry, Notification newNotification) {
-        return oldEntry == null || !oldEntry.hasInterrupted()
-                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
-    }
-
-    private OnHeadsUpChangedListener mOnHeadsUpChangedListener  = new OnHeadsUpChangedListener() {
-        @Override
-        public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
-            if (!isHeadsUp && !entry.getRow().isRemoved()) {
-                mHeadsUpViewBinder.unbindHeadsUpView(entry);
-            }
-        }
-    };
-
-    private static final String TAG = "HeadsUpBindController";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 5ef2b9e..6f41425 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -41,8 +41,7 @@
  * figuring out the right heads up inflation parameters and inflating/freeing the heads up
  * content view.
  *
- * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
- * (i.e. when {@link HeadsUpController} is removed).
+ * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated.
  */
 @SysUISingleton
 public class HeadsUpViewBinder {
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 3c01802..9ad906c 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
@@ -94,7 +94,6 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-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.NotificationCounters;
@@ -904,21 +903,6 @@
         return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
     }
 
-    /**
-     * Apply the order given in the list to the children.
-     *
-     * @param childOrder the new list order
-     * @param visualStabilityManager
-     * @param callback the callback to invoked in case it is not allowed
-     * @return whether the list order has changed
-     */
-    public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
-            VisualStabilityManager visualStabilityManager,
-            VisualStabilityManager.Callback callback) {
-        return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
-                visualStabilityManager, callback);
-    }
-
     /** Updates states of all children. */
     public void updateChildrenStates(AmbientState ambientState) {
         if (mIsSummaryWithChildren) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index c4ff259..7b0b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -55,7 +55,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
@@ -81,8 +80,7 @@
  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
  * closing guts, and keeping track of the currently exposed notification guts.
  */
-public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
-        NotifGutsViewManager {
+public class NotificationGutsManager implements NotifGutsViewManager {
     private static final String TAG = "NotificationGutsManager";
 
     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -107,13 +105,10 @@
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
-    private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
     private NotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private NotificationListContainer mListContainer;
     private OnSettingsClickListener mOnSettingsClickListener;
-    @VisibleForTesting
-    protected String mKeyToRemoveOnGutsClosed;
 
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final Handler mMainHandler;
@@ -148,8 +143,7 @@
             Optional<BubblesManager> bubblesManagerOptional,
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback,
-            ShadeController shadeController,
-            DumpManager dumpManager) {
+            ShadeController shadeController) {
         mContext = context;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mMainHandler = mainHandler;
@@ -167,8 +161,6 @@
         mUiEventLogger = uiEventLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mShadeController = shadeController;
-
-        dumpManager.registerDumpable(this);
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -266,12 +258,6 @@
                 mGutsListener.onGutsClose(entry);
             }
             String key = entry.getKey();
-            if (key.equals(mKeyToRemoveOnGutsClosed)) {
-                mKeyToRemoveOnGutsClosed = null;
-                if (mNotificationLifetimeFinishedCallback != null) {
-                    mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
-                }
-            }
         });
 
         View gutsView = item.getGutsView();
@@ -658,46 +644,6 @@
         return true;
     }
 
-    @Override
-    public void setCallback(NotificationSafeToRemoveCallback callback) {
-        mNotificationLifetimeFinishedCallback = callback;
-    }
-
-    @Override
-    public boolean shouldExtendLifetime(NotificationEntry entry) {
-        return entry != null
-                &&(mNotificationGutsExposed != null
-                    && entry.getGuts() != null
-                    && mNotificationGutsExposed == entry.getGuts()
-                    && !mNotificationGutsExposed.isLeavebehind());
-    }
-
-    @Override
-    public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
-        if (shouldExtend) {
-            mKeyToRemoveOnGutsClosed = entry.getKey();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Keeping notification because it's showing guts. " + entry.getKey());
-            }
-        } else {
-            if (mKeyToRemoveOnGutsClosed != null
-                    && mKeyToRemoveOnGutsClosed.equals(entry.getKey())) {
-                mKeyToRemoveOnGutsClosed = null;
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Notification that was kept for guts was updated. "
-                            + entry.getKey());
-                }
-            }
-        }
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("NotificationGutsManager state:");
-        pw.print("  mKeyToRemoveOnGutsClosed (legacy): ");
-        pw.println(mKeyToRemoveOnGutsClosed);
-    }
-
     /**
      * @param gutsListener the listener for open and close guts events
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a76f0827..d77e03f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -43,7 +43,6 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
@@ -462,39 +461,6 @@
         return mAttachedChildren;
     }
 
-    /**
-     * Apply the order given in the list to the children.
-     *
-     * @param childOrder the new list order
-     * @param visualStabilityManager
-     * @param callback
-     * @return whether the list order has changed
-     */
-    public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
-            VisualStabilityManager visualStabilityManager,
-            VisualStabilityManager.Callback callback) {
-        if (childOrder == null) {
-            return false;
-        }
-        boolean result = false;
-        for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
-            ExpandableNotificationRow child = mAttachedChildren.get(i);
-            ExpandableNotificationRow desiredChild = childOrder.get(i);
-            if (child != desiredChild) {
-                if (visualStabilityManager.canReorderNotification(desiredChild)) {
-                    mAttachedChildren.remove(desiredChild);
-                    mAttachedChildren.add(i, desiredChild);
-                    result = true;
-                } else {
-                    visualStabilityManager.addReorderingAllowedCallback(callback,
-                            false /* persistent */);
-                }
-            }
-        }
-        updateExpansionStates();
-        return result;
-    }
-
     /** To be called any time the rows have been updated */
     public void updateExpansionStates() {
         if (mChildrenExpanded || mUserLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 54e26c3..91a2813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -20,9 +20,6 @@
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.media.KeyguardMediaController
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -33,32 +30,20 @@
 import com.android.systemui.statusbar.notification.dagger.SilentHeader
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.children
 import com.android.systemui.util.foldToSparseArray
-import com.android.systemui.util.takeUntil
-import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
- * Manages the boundaries of the notification sections (incoming, conversations, high priority, and
- * low priority).
- *
- * In the legacy notification pipeline, this is responsible for correctly positioning all section
- * headers after the [NotificationStackScrollLayout] has had notifications added/removed/changed. In
- * the new pipeline, this is handled as part of the [ShadeViewManager].
+ * Manages section headers in the NSSL.
  *
  * TODO: Move remaining sections logic from NSSL into this class.
  */
 class NotificationSectionsManager @Inject internal constructor(
-    private val statusBarStateController: StatusBarStateController,
     private val configurationController: ConfigurationController,
     private val keyguardMediaController: KeyguardMediaController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val logger: NotificationSectionsLogger,
-    private val notifPipelineFlags: NotifPipelineFlags,
     private val mediaContainerController: MediaContainerController,
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
     @PeopleHeader private val peopleHeaderController: SectionHeaderController,
@@ -139,205 +124,6 @@
         else -> null
     }
 
-    private fun logShadeChild(i: Int, child: View) {
-        when {
-            child === incomingHeaderView -> logger.logIncomingHeader(i)
-            child === mediaControlsView -> logger.logMediaControls(i)
-            child === peopleHeaderView -> logger.logConversationsHeader(i)
-            child === alertingHeaderView -> logger.logAlertingHeader(i)
-            child === silentHeaderView -> logger.logSilentHeader(i)
-            child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
-            else -> {
-                val isHeadsUp = child.isHeadsUp
-                when (child.entry.bucket) {
-                    BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
-                    BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
-                    BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
-                    BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
-                }
-            }
-        }
-    }
-    private fun logShadeContents() = traceSection("NotifSectionsManager.logShadeContents") {
-        parent.children.forEachIndexed(::logShadeChild)
-    }
-
-    private val isUsingMultipleSections: Boolean
-        get() = sectionsFeatureManager.getNumberOfBuckets() > 1
-
-    @VisibleForTesting
-    fun updateSectionBoundaries() = updateSectionBoundaries("test")
-
-    private interface SectionUpdateState<out T : ExpandableView> {
-        val header: T
-        var currentPosition: Int?
-        var targetPosition: Int?
-        fun adjustViewPosition()
-    }
-
-    private fun <T : ExpandableView> expandableViewHeaderState(header: T): SectionUpdateState<T> =
-            object : SectionUpdateState<T> {
-                override val header = header
-                override var currentPosition: Int? = null
-                override var targetPosition: Int? = null
-
-                override fun adjustViewPosition() {
-                    notifPipelineFlags.checkLegacyPipelineEnabled()
-                    val target = targetPosition
-                    val current = currentPosition
-                    if (target == null) {
-                        if (current != null) {
-                            parent.removeView(header)
-                        }
-                    } else {
-                        if (current == null) {
-                            // If the header is animating away, it will still have a parent, so
-                            // detach it first
-                            // TODO: We should really cancel the active animations here. This will
-                            //  happen automatically when the view's intro animation starts, but
-                            //  it's a fragile link.
-                            header.removeFromTransientContainer()
-                            parent.addView(header, target)
-                        } else {
-                            parent.changeViewPosition(header, target)
-                        }
-                    }
-                }
-    }
-
-    private fun <T : StackScrollerDecorView> decorViewHeaderState(
-        header: T
-    ): SectionUpdateState<T> {
-        notifPipelineFlags.checkLegacyPipelineEnabled()
-        val inner = expandableViewHeaderState(header)
-        return object : SectionUpdateState<T> by inner {
-            override fun adjustViewPosition() {
-                inner.adjustViewPosition()
-                if (targetPosition != null && currentPosition == null) {
-                    header.isContentVisible = true
-                }
-            }
-        }
-    }
-
-    /**
-     * Should be called whenever notifs are added, removed, or updated. Updates section boundary
-     * bookkeeping and adds/moves/removes section headers if appropriate.
-     */
-    fun updateSectionBoundaries(reason: String) = traceSection("NotifSectionsManager.update") {
-        notifPipelineFlags.checkLegacyPipelineEnabled()
-        if (!isUsingMultipleSections) {
-            return@traceSection
-        }
-        logger.logStartSectionUpdate(reason)
-
-        // The overall strategy here is to iterate over the current children of mParent, looking
-        // for where the sections headers are currently positioned, and where each section begins.
-        // Then, once we find the start of a new section, we track that position as the "target" for
-        // the section header, adjusted for the case where existing headers are in front of that
-        // target, but won't be once they are moved / removed after the pass has completed.
-
-        val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
-        val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
-
-        val mediaState = mediaControlsView?.let(::expandableViewHeaderState)
-        val incomingState = incomingHeaderView?.let(::decorViewHeaderState)
-        val peopleState = peopleHeaderView?.let(::decorViewHeaderState)
-        val alertingState = alertingHeaderView?.let(::decorViewHeaderState)
-        val gentleState = silentHeaderView?.let(::decorViewHeaderState)
-
-        fun getSectionState(view: View): SectionUpdateState<ExpandableView>? = when {
-            view === mediaControlsView -> mediaState
-            view === incomingHeaderView -> incomingState
-            view === peopleHeaderView -> peopleState
-            view === alertingHeaderView -> alertingState
-            view === silentHeaderView -> gentleState
-            else -> null
-        }
-
-        val headersOrdered = sequenceOf(
-                mediaState, incomingState, peopleState, alertingState, gentleState
-        ).filterNotNull()
-
-        var peopleNotifsPresent = false
-        var nextBucket: Int? = null
-        var inIncomingSection = false
-
-        // Iterating backwards allows for easier construction of the Incoming section, as opposed
-        // to backtracking when a discontinuity in the sections is discovered.
-        // Iterating to -1 in order to support the case where a header is at the very top of the
-        // shade.
-        for (i in parent.childCount - 1 downTo -1) {
-            val child: View? = parent.getChildAt(i)
-
-            child?.let {
-                logShadeChild(i, child)
-                // If this child is a header, update the tracked positions
-                getSectionState(child)?.let { state ->
-                    state.currentPosition = i
-                    // If headers that should appear above this one in the shade already have a
-                    // target index, then we need to decrement them in order to account for this one
-                    // being either removed, or moved below them.
-                    headersOrdered.takeUntil { it === state }
-                            .forEach { it.targetPosition = it.targetPosition?.minus(1) }
-                }
-            }
-
-            val row = (child as? ExpandableNotificationRow)
-                    ?.takeUnless { it.visibility == View.GONE }
-
-            // Is there a section discontinuity? This usually occurs due to HUNs
-            inIncomingSection = inIncomingSection || nextBucket?.let { next ->
-                row?.entry?.bucket?.let { curr -> next < curr }
-            } == true
-
-            if (inIncomingSection) {
-                // Update the bucket to reflect that it's being placed in the Incoming section
-                row?.entry?.bucket = BUCKET_HEADS_UP
-            }
-
-            // Insert a header in front of the next row, if there's a boundary between it and this
-            // row, or if it is the topmost row.
-            val isSectionBoundary = nextBucket != null &&
-                    (child == null || row != null && nextBucket != row.entry.bucket)
-            if (isSectionBoundary && showHeaders) {
-                when (nextBucket) {
-                    BUCKET_SILENT -> gentleState?.targetPosition = i + 1
-                }
-            }
-
-            row ?: continue
-
-            // Check if there are any people notifications
-            peopleNotifsPresent = peopleNotifsPresent || row.entry.bucket == BUCKET_PEOPLE
-            nextBucket = row.entry.bucket
-        }
-
-        mediaState?.targetPosition = if (usingMediaControls) 0 else null
-
-        logger.logStr("New header target positions:")
-        logger.logMediaControls(mediaState?.targetPosition ?: -1)
-        logger.logIncomingHeader(incomingState?.targetPosition ?: -1)
-        logger.logConversationsHeader(peopleState?.targetPosition ?: -1)
-        logger.logAlertingHeader(alertingState?.targetPosition ?: -1)
-        logger.logSilentHeader(gentleState?.targetPosition ?: -1)
-
-        // Update headers in reverse order to preserve indices, otherwise movements earlier in the
-        // list will affect the target indices of the headers later in the list.
-        headersOrdered.asIterable().reversed().forEach { it.adjustViewPosition() }
-
-        logger.logStr("Final order:")
-        logShadeContents()
-        logger.logStr("Section boundary update complete")
-
-        // Update headers to reflect state of section contents
-        silentHeaderView?.run {
-            val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
-                    .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
-            setClearSectionButtonEnabled(hasActiveClearableNotifications)
-        }
-    }
-
     private sealed class SectionBounds {
 
         data class Many(
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 427004e..952bafb 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
@@ -5822,12 +5822,6 @@
         mSpeedBumpIndexDirty = true;
     }
 
-    /** Updates the indices of the boundaries between sections. */
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    public void updateSectionBoundaries(String reason) {
-        mSectionsManager.updateSectionBoundaries(reason);
-    }
-
     void updateContinuousBackgroundDrawing() {
         boolean continuousBackground = !mAmbientState.isFullyAwake()
                 && mSwipeHelper.isSwiping();
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 cc539b0..9998fe4 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
@@ -90,7 +90,6 @@
 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.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -162,7 +161,6 @@
     private final NotificationEntryManager mNotificationEntryManager;
     private final UiEventLogger mUiEventLogger;
     private final NotificationRemoteInputManager mRemoteInputManager;
-    private final VisualStabilityManager mVisualStabilityManager;
     private final ShadeController mShadeController;
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -646,7 +644,6 @@
             ShadeTransitionController shadeTransitionController,
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
-            VisualStabilityManager visualStabilityManager,
             ShadeController shadeController,
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
@@ -693,7 +690,6 @@
         mNotificationEntryManager = notificationEntryManager;
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
-        mVisualStabilityManager = visualStabilityManager;
         mShadeController = shadeController;
         updateResources();
     }
@@ -765,8 +761,6 @@
         mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
         mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
 
-        mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
-
         mTunerService.addTunable(
                 (key, newValue) -> {
                     switch (key) {
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 a5bf88e..b0a5adb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -206,7 +206,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -689,7 +688,6 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
-            VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             AccessibilityFloatingMenuController accessibilityFloatingMenuController,
@@ -777,7 +775,6 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
-        mVisualStabilityManager = visualStabilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
         mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
@@ -3914,9 +3911,6 @@
     // all notifications
     protected NotificationStackScrollLayout mStackScroller;
 
-    // handling reordering
-    private final VisualStabilityManager mVisualStabilityManager;
-
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index b99b4ab..4c9c75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -330,14 +330,6 @@
         dumpInternal(pw, args);
     }
 
-    @Override
-    public boolean shouldExtendLifetime(NotificationEntry entry) {
-        // We should not defer the removal if reordering isn't allowed since otherwise
-        // these won't disappear until reordering is allowed again, which happens only once
-        // the notification panel is collapsed again.
-        return mVisualStabilityProvider.isReorderingAllowed() && super.shouldExtendLifetime(entry);
-    }
-
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  OnReorderingAllowedListener:
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 339f371..d24469e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -39,6 +39,8 @@
  * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
  */
 public class KeyguardIndicationTextView extends TextView {
+    public static final long Y_IN_DURATION = 600L;
+
     @StyleRes
     private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
     @StyleRes
@@ -259,7 +261,7 @@
 
     private long getYInDuration() {
         if (!mAnimationsEnabled) return 0L;
-        return 600L;
+        return Y_IN_DURATION;
     }
 
     private long getFadeOutDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
index 96b9aca..2763750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.statusbar.phone
 
 import android.annotation.ColorInt
+import android.app.WallpaperManager
 import android.graphics.Color
+import android.os.Handler
 import android.os.RemoteException
 import android.view.IWindowManager
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -37,6 +40,8 @@
     private val windowManager: IWindowManager,
     @Background private val backgroundExecutor: Executor,
     private val dumpManager: DumpManager,
+    private val wallpaperManager: WallpaperManager,
+    @Main private val mainHandler: Handler,
 ) : CentralSurfacesComponent.Startable, Dumpable {
 
     @ColorInt
@@ -46,9 +51,18 @@
     var isLetterboxBackgroundMultiColored: Boolean = false
         private set
 
+    private val wallpaperColorsListener =
+        WallpaperManager.OnColorsChangedListener { _, _ ->
+            fetchBackgroundColorInfo()
+        }
+
     override fun start() {
         dumpManager.registerDumpable(javaClass.simpleName, this)
+        fetchBackgroundColorInfo()
+        wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler)
+    }
 
+    private fun fetchBackgroundColorInfo() {
         // Using a background executor, as binder calls to IWindowManager are blocking
         backgroundExecutor.execute {
             try {
@@ -62,6 +76,7 @@
 
     override fun stop() {
         dumpManager.unregisterDumpable(javaClass.simpleName)
+        wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener)
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
deleted file mode 100644
index ca6e67e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ /dev/null
@@ -1,717 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
-import static com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.logGroupKey;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.os.SystemClock;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.util.Compile;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-/**
- * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
- * and {@link HeadsUpManager}. In particular, this class deals with keeping
- * the correct notification in a group alerting based off the group suppression and alertOverride.
- */
-@SysUISingleton
-public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
-        StateListener {
-
-    private static final long ALERT_TRANSFER_TIMEOUT = 300;
-    private static final String TAG = "NotifGroupAlertTransfer";
-    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 list of entries containing group alert metadata for each group. Keyed by group key.
-     */
-    private final ArrayMap<String, GroupAlertEntry> mGroupAlertEntries = new ArrayMap<>();
-
-    /**
-     * The list of entries currently inflating that should alert after inflation. Keyed by
-     * notification key.
-     */
-    private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
-
-    private HeadsUpManager mHeadsUpManager;
-    private final RowContentBindStage mRowContentBindStage;
-    private final NotificationGroupManagerLegacy mGroupManager;
-
-    private NotificationEntryManager mEntryManager;
-
-    private boolean mIsDozing;
-
-    /**
-     * Injected constructor. See {@link StatusBarPhoneModule}.
-     */
-    @Inject
-    public NotificationGroupAlertTransferHelper(
-            RowContentBindStage bindStage,
-            StatusBarStateController statusBarStateController,
-            NotificationGroupManagerLegacy notificationGroupManagerLegacy) {
-        mRowContentBindStage = bindStage;
-        mGroupManager = notificationGroupManagerLegacy;
-        statusBarStateController.addCallback(this);
-    }
-
-    /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
-    public void bind(NotificationEntryManager entryManager,
-            NotificationGroupManagerLegacy groupManager) {
-        if (mEntryManager != null) {
-            throw new IllegalStateException("Already bound.");
-        }
-
-        // TODO(b/119637830): It would be good if GroupManager already had all pending notifications
-        // as normal children (i.e. add notifications to GroupManager before inflation) so that we
-        // don't have to have this dependency. We'd also have to worry less about the suppression
-        // not being up to date.
-        mEntryManager = entryManager;
-
-        mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
-        groupManager.registerGroupChangeListener(mOnGroupChangeListener);
-    }
-
-    /**
-     * Whether or not a notification has transferred its alert state to the notification and
-     * the notification should alert after inflating.
-     *
-     * @param entry notification to check
-     * @return true if the entry was transferred to and should inflate + alert
-     */
-    public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
-        PendingAlertInfo alertInfo = mPendingAlerts.get(entry.getKey());
-        return alertInfo != null && alertInfo.isStillValid();
-    }
-
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
-    @Override
-    public void onStateChanged(int newState) {}
-
-    @Override
-    public void onDozingChanged(boolean isDozing) {
-        if (mIsDozing != isDozing) {
-            for (GroupAlertEntry groupAlertEntry : mGroupAlertEntries.values()) {
-                groupAlertEntry.mLastAlertTransferTime = 0;
-                groupAlertEntry.mAlertSummaryOnNextAddition = false;
-            }
-        }
-        mIsDozing = isDozing;
-    }
-
-    private final NotificationGroupManagerLegacy.OnGroupChangeListener mOnGroupChangeListener =
-            new NotificationGroupManagerLegacy.OnGroupChangeListener() {
-        @Override
-        public void onGroupCreated(NotificationGroup group, String groupKey) {
-            mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
-        }
-
-        @Override
-        public void onGroupRemoved(NotificationGroup group, String groupKey) {
-            mGroupAlertEntries.remove(groupKey);
-        }
-
-        @Override
-        public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onGroupSuppressionChanged:"
-                        + " group=" + logGroupKey(group)
-                        + " group.summary=" + logKey(group.summary)
-                        + " suppressed=" + suppressed);
-            }
-            NotificationEntry oldAlertOverride = group.alertOverride;
-            onGroupChanged(group, oldAlertOverride);
-        }
-
-        @Override
-        public void onGroupAlertOverrideChanged(NotificationGroup group,
-                @Nullable NotificationEntry oldAlertOverride,
-                @Nullable NotificationEntry newAlertOverride) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onGroupAlertOverrideChanged:"
-                        + " group=" + logGroupKey(group)
-                        + " group.summary=" + logKey(group.summary)
-                        + " oldAlertOverride=" + logKey(oldAlertOverride)
-                        + " newAlertOverride=" + logKey(newAlertOverride));
-            }
-            onGroupChanged(group, oldAlertOverride);
-        }
-    };
-
-    /**
-     * Called when either the suppressed or alertOverride fields of the group changed
-     *
-     * @param group the group which changed
-     * @param oldAlertOverride the previous value of group.alertOverride
-     */
-    private void onGroupChanged(NotificationGroup group,
-            NotificationEntry oldAlertOverride) {
-        // Group summary can be null if we are no longer suppressed because the summary was
-        // removed. In that case, we don't need to alert the summary.
-        if (group.summary == null) {
-            if (DEBUG) {
-                Log.d(TAG, "onGroupChanged: summary is null");
-            }
-            return;
-        }
-        if (group.suppressed || group.alertOverride != null) {
-            checkForForwardAlertTransfer(group.summary, oldAlertOverride);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "onGroupChanged: maybe transfer back");
-            }
-            GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
-                    group.summary.getSbn()));
-            // Group is no longer suppressed or overridden.
-            // We should check if we need to transfer the alert back to the summary.
-            if (groupAlertEntry.mAlertSummaryOnNextAddition) {
-                if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
-                    alertNotificationWhenPossible(group.summary);
-                }
-                groupAlertEntry.mAlertSummaryOnNextAddition = false;
-            } else {
-                checkShouldTransferBack(groupAlertEntry);
-            }
-        }
-    }
-
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        if (DEBUG) {
-            Log.d(TAG, "!! onHeadsUpStateChanged:"
-                    + " entry=" + logKey(entry)
-                    + " isHeadsUp=" + isHeadsUp);
-        }
-        if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
-            // a group summary is alerting; trigger the forward transfer checks
-            checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
-        }
-    }
-
-    /**
-     * Handles changes in a group's suppression or alertOverride, but where at least one of those
-     * conditions is still true (either the group is suppressed, the group has an alertOverride,
-     * or both).  The method determined which kind of child needs to receive the alert, finds the
-     * entry currently alerting, and makes the transfer.
-     *
-     * Internally, this is handled with two main cases: the override needs the alert, or there is
-     * no override but the summary is suppressed (so an isolated child needs the alert).
-     *
-     * @param summary the notification entry of the summary of the logical group.
-     * @param oldAlertOverride the former value of group.alertOverride, before whatever event
-     *                         required us to check for for a transfer condition.
-     */
-    private void checkForForwardAlertTransfer(NotificationEntry summary,
-            NotificationEntry oldAlertOverride) {
-        if (DEBUG) {
-            Log.d(TAG, "checkForForwardAlertTransfer: enter");
-        }
-        NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
-        if (group != null && group.alertOverride != null) {
-            handleOverriddenSummaryAlerted(summary);
-        } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
-            handleSuppressedSummaryAlerted(summary, oldAlertOverride);
-        }
-        if (DEBUG) {
-            Log.d(TAG, "checkForForwardAlertTransfer: done");
-        }
-    }
-
-    private final NotificationEntryListener mNotificationEntryListener =
-            new NotificationEntryListener() {
-        // Called when a new notification has been posted but is not inflated yet. We use this to
-        // see as early as we can if we need to abort a transfer.
-        @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onPendingEntryAdded: entry=" + logKey(entry));
-            }
-            String groupKey = mGroupManager.getGroupKey(entry.getSbn());
-            GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
-            if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
-                // new pending group entries require us to transfer back from the child to the
-                // group, but alertOverrides are only present in very limited circumstances, so
-                // while it's possible the group should ALSO alert, the previous detection which set
-                // this alertOverride won't be invalidated by this notification added to this group.
-                checkShouldTransferBack(groupAlertEntry);
-            }
-        }
-
-        @Override
-        public void onEntryRemoved(
-                @Nullable NotificationEntry entry,
-                NotificationVisibility visibility,
-                boolean removedByUser,
-                int reason) {
-            // Removes any alerts pending on this entry. Note that this will not stop any inflation
-            // tasks started by a transfer, so this should only be used as clean-up for when
-            // inflation is stopped and the pending alert no longer needs to happen.
-            mPendingAlerts.remove(entry.getKey());
-        }
-    };
-
-    /**
-     * Gets the number of new notifications pending inflation that will be added to the group
-     * but currently aren't and should not alert.
-     *
-     * @param group group to check
-     * @return the number of new notifications that will be added to the group
-     */
-    private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
-        if (mEntryManager == null) {
-            return 0;
-        }
-        int number = 0;
-        Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
-        for (NotificationEntry entry : values) {
-            if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
-                number++;
-            }
-        }
-        return number;
-    }
-
-    /**
-     * Checks if the pending inflations will add children to this group.
-     *
-     * @param group group to check
-     * @return true if a pending notification will add to this group
-     */
-    private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
-        if (mEntryManager == null) {
-            return false;
-        }
-        Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
-        for (NotificationEntry entry : values) {
-            if (isPendingNotificationInGroup(entry, group)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Checks if a new pending notification will be added to the group.
-     *
-     * @param entry pending notification
-     * @param group group to check
-     * @return true if the notification will add to the group, false o/w
-     */
-    private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
-            @NonNull NotificationGroup group) {
-        String groupKey = mGroupManager.getGroupKey(group.summary.getSbn());
-        return mGroupManager.isGroupChild(entry.getSbn())
-                && Objects.equals(mGroupManager.getGroupKey(entry.getSbn()), groupKey)
-                && !group.children.containsKey(entry.getKey());
-    }
-
-    /**
-     * Handles the scenario where a summary that has been suppressed is itself, or has a former
-     * alertOverride (in the form of an isolated logical child) which was alerted.  A suppressed
-     * summary should for all intents and purposes be invisible to the user and as a result should
-     * not alert.  When this is the case, it is our responsibility to pass the alert to the
-     * appropriate child which will be the representative notification alerting for the group.
-     *
-     * @param summary the summary that is suppressed and (potentially) alerting
-     * @param oldAlertOverride the alertOverride before whatever event triggered this method.  If
-     *                         the alert override was removed, this will be the entry that should
-     *                         be transferred back from.
-     */
-    private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
-            NotificationEntry oldAlertOverride) {
-        if (DEBUG) {
-            Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + logKey(summary));
-        }
-        GroupAlertEntry groupAlertEntry =
-                mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-
-        if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
-                || groupAlertEntry == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
-            }
-            return;
-        }
-        boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
-        boolean priorityIsAlerting = oldAlertOverride != null
-                && mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
-        if (!summaryIsAlerting && !priorityIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
-            }
-            return;
-        }
-
-        if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
-            // New children will actually be added to this group, let's not transfer the alert.
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
-            }
-            return;
-        }
-
-        NotificationEntry child =
-                mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
-        if (summaryIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
-            }
-            tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
-            return;
-        }
-        // Summary didn't have the alert, so we're in "transfer back" territory.  First, make sure
-        // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
-        // the isolated child which should receive the alert.
-        if (!canStillTransferBack(groupAlertEntry)) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
-            }
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
-        }
-        tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
-    }
-
-    /**
-     * Checks for and handles the scenario where the given entry is the summary of a group which
-     * has an alertOverride, and either the summary itself or one of its logical isolated children
-     * is currently alerting (which happens if the summary is suppressed).
-     */
-    private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
-        if (DEBUG) {
-            Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + logKey(summary));
-        }
-        GroupAlertEntry groupAlertEntry =
-                mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-        NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
-        if (group == null || group.alertOverride == null || groupAlertEntry == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
-            }
-            return;
-        }
-        boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
-        if (summaryIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
-            }
-            tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
-            return;
-        }
-        // Summary didn't have the alert, so we're in "transfer back" territory.  First, make sure
-        // it's not too late to transfer back, then remove the alert from any of the logical
-        // children, and if one of them was alerting, we can alert the override.
-        if (!canStillTransferBack(groupAlertEntry)) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
-            }
-            return;
-        }
-        List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
-        if (children == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
-            }
-            return;
-        }
-        children.remove(group.alertOverride); // do not release the alert on our desired destination
-        boolean releasedChild = releaseChildAlerts(children);
-        if (releasedChild) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
-            }
-            tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
-            }
-        }
-    }
-
-    /**
-     * Transfers the alert state one entry to another. We remove the alert from the first entry
-     * immediately to have the incorrect one up as short as possible. The second should alert
-     * when possible.
-     *
-     * @param summary entry of the summary
-     * @param fromEntry entry to transfer alert from
-     * @param toEntry entry to transfer to
-     */
-    private void tryTransferAlertState(
-            NotificationEntry summary,
-            NotificationEntry fromEntry,
-            NotificationEntry toEntry,
-            GroupAlertEntry groupAlertEntry) {
-        if (toEntry != null) {
-            if (toEntry.getRow().keepInParent()
-                    || toEntry.isRowRemoved()
-                    || toEntry.isRowDismissed()) {
-                // The notification is actually already removed. No need to alert it.
-                return;
-            }
-            if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
-                groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
-            }
-            if (DEBUG) {
-                Log.d(TAG, "transferAlertState:"
-                        + " fromEntry=" + logKey(fromEntry)
-                        + " toEntry=" + logKey(toEntry));
-            }
-            transferAlertState(fromEntry, toEntry);
-        }
-    }
-    private void transferAlertState(@Nullable NotificationEntry fromEntry,
-            @NonNull NotificationEntry toEntry) {
-        if (fromEntry != null) {
-            mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
-        }
-        alertNotificationWhenPossible(toEntry);
-    }
-
-    /**
-     * Determines if we need to transfer the alert back to the summary from the child and does
-     * so if needed.
-     *
-     * This can happen since notification groups are not delivered as a whole unit and it is
-     * possible we erroneously transfer the alert from the summary to the child even though
-     * more children are coming. Thus, if a child is added within a certain timeframe after we
-     * transfer, we back out and alert the summary again.
-     *
-     * An alert can only transfer back within a small window of time after a transfer away from the
-     * summary to a child happened.
-     *
-     * @param groupAlertEntry group alert entry to check
-     */
-    private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
-        if (canStillTransferBack(groupAlertEntry)) {
-            NotificationEntry summary = groupAlertEntry.mGroup.summary;
-
-            if (!onlySummaryAlerts(summary)) {
-                return;
-            }
-            ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
-                    summary.getSbn());
-            int numActiveChildren = children.size();
-            int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
-            int numChildren = numActiveChildren + numPendingChildren;
-            if (numChildren <= 1) {
-                return;
-            }
-            boolean releasedChild = releaseChildAlerts(children);
-            if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
-                boolean notifyImmediately = numActiveChildren > 1;
-                if (notifyImmediately) {
-                    alertNotificationWhenPossible(summary);
-                } else {
-                    // Should wait until the pending child inflates before alerting.
-                    groupAlertEntry.mAlertSummaryOnNextAddition = true;
-                }
-                groupAlertEntry.mLastAlertTransferTime = 0;
-            }
-        }
-    }
-
-    private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
-        return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
-                < ALERT_TRANSFER_TIMEOUT;
-    }
-
-    private boolean releaseChildAlerts(List<NotificationEntry> children) {
-        boolean releasedChild = false;
-        if (SPEW) {
-            Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
-        }
-        for (int i = 0; i < children.size(); i++) {
-            NotificationEntry entry = children.get(i);
-            if (SPEW) {
-                Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
-                        + " onlySummaryAlerts=" + onlySummaryAlerts(entry)
-                        + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
-                        + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
-            }
-            if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
-                releasedChild = true;
-                mHeadsUpManager.removeNotification(
-                        entry.getKey(), true /* releaseImmediately */);
-            }
-            if (mPendingAlerts.containsKey(entry.getKey())) {
-                // This is the child that would've been removed if it was inflated.
-                releasedChild = true;
-                mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
-            }
-        }
-        if (SPEW) {
-            Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
-        }
-        return releasedChild;
-    }
-
-    /**
-     * Tries to alert the notification. If its content view is not inflated, we inflate and continue
-     * when the entry finishes inflating the view.
-     *
-     * @param entry entry to show
-     */
-    private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
-        @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
-        final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
-        if ((params.getContentViews() & contentFlag) == 0) {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible:"
-                        + " async requestRebind entry=" + logKey(entry));
-            }
-            mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
-            params.requireContentViews(contentFlag);
-            mRowContentBindStage.requestRebind(entry, en -> {
-                PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
-                if (alertInfo != null) {
-                    if (alertInfo.isStillValid()) {
-                        alertNotificationWhenPossible(entry);
-                    } else {
-                        if (DEBUG) {
-                            Log.d(TAG, "alertNotificationWhenPossible:"
-                                    + " markContentViewsFreeable entry=" + logKey(entry));
-                        }
-                        // The transfer is no longer valid. Free the content.
-                        mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
-                                contentFlag);
-                        mRowContentBindStage.requestRebind(entry, null);
-                    }
-                }
-            });
-            return;
-        }
-        if (mHeadsUpManager.isAlerting(entry.getKey())) {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible:"
-                        + " continue alerting entry=" + logKey(entry));
-            }
-            mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible:"
-                        + " start alerting entry=" + logKey(entry));
-            }
-            mHeadsUpManager.showNotification(entry);
-        }
-    }
-
-    private boolean onlySummaryAlerts(NotificationEntry entry) {
-        return entry.getSbn().getNotification().getGroupAlertBehavior()
-                == Notification.GROUP_ALERT_SUMMARY;
-    }
-
-    /**
-     * Information about a pending alert used to determine if the alert is still needed when
-     * inflation completes.
-     */
-    private class PendingAlertInfo {
-
-        /**
-         * The original notification when the transfer is initiated. This is used to determine if
-         * the transfer is still valid if the notification is updated.
-         */
-        final StatusBarNotification mOriginalNotification;
-        final NotificationEntry mEntry;
-
-        /**
-         * The notification is still pending inflation but we've decided that we no longer need
-         * the content view (e.g. suppression might have changed and we decided we need to transfer
-         * back).
-         *
-         * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
-         */
-        boolean mAbortOnInflation;
-
-        PendingAlertInfo(NotificationEntry entry) {
-            mOriginalNotification = entry.getSbn();
-            mEntry = entry;
-        }
-
-        /**
-         * Whether or not the pending alert is still valid and should still alert after inflation.
-         *
-         * @return true if the pending alert should still occur, false o/w
-         */
-        private boolean isStillValid() {
-            if (mAbortOnInflation) {
-                // Notification is aborted due to the transfer being explicitly cancelled
-                return false;
-            }
-            if (!mEntry.getSbn().getGroupKey().equals(mOriginalNotification.getGroupKey())) {
-                // Groups have changed
-                return false;
-            }
-            if (mEntry.getSbn().getNotification().isGroupSummary()
-                    != mOriginalNotification.getNotification().isGroupSummary()) {
-                // Notification has changed from group summary to not or vice versa
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Contains alert metadata for the notification group used to determine when/how the alert
-     * should be transferred.
-     */
-    private static class GroupAlertEntry {
-        /**
-         * The time when the last alert transfer from summary to child happened.
-         */
-        long mLastAlertTransferTime;
-        boolean mAlertSummaryOnNextAddition;
-        final NotificationGroup mGroup;
-
-        GroupAlertEntry(NotificationGroup group) {
-            this.mGroup = group;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 05d087e..0a44bda 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -37,5 +37,5 @@
     @Provides
     @SysUISingleton
     @Background
-    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.Default
+    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c677371..cde30af 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -1005,7 +1006,7 @@
 
         // WHEN udfps is now enrolled
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
-        callback.onEnrollmentsChanged();
+        callback.onEnrollmentsChanged(TYPE_FINGERPRINT);
 
         // THEN isUdfspEnrolled is TRUE
         assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
index 6f4846a..36ae3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -27,6 +28,7 @@
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
@@ -52,6 +54,7 @@
     private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     private MagnificationGestureDetector mGestureDetector;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+    private View mSpyView;
     @Mock
     private MagnificationGestureDetector.OnGestureListener mListener;
     @Mock
@@ -66,6 +69,7 @@
             return null;
         }).when(mHandler).postAtTime(any(Runnable.class), anyLong());
         mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);
+        mSpyView = Mockito.spy(new View(mContext));
     }
 
     @After
@@ -79,7 +83,7 @@
         final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
 
         mListener.onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
     }
@@ -92,14 +96,14 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
-        inOrder.verify(mListener).onSingleTap();
+        inOrder.verify(mListener).onSingleTap(mSpyView);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onDrag(anyFloat(), anyFloat());
+        verify(mListener, never()).onDrag(eq(mSpyView), anyFloat(), anyFloat());
     }
 
     @Test
@@ -110,10 +114,10 @@
         final MotionEvent cancelEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_CANCEL, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(cancelEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, cancelEvent);
 
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -124,10 +128,10 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_POINTER_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -138,15 +142,15 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
         // Execute the pending message for stopping single-tap detection.
         mCancelSingleTapRunnable.run();
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -160,14 +164,14 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(moveEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, moveEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
-        inOrder.verify(mListener).onDrag(dragOffset, 0);
+        inOrder.verify(mListener).onDrag(mSpyView, dragOffset, 0);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index bc89da7..00cb491 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -145,7 +145,7 @@
 
     @Test
     public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         mMagnificationModeSwitch.removeButton();
 
@@ -167,7 +167,7 @@
 
     @Test
     public void showButton_excludeSystemGestureArea() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
     }
@@ -178,7 +178,7 @@
         when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
                 a11yTimeout);
 
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mAccessibilityManager).getRecommendedTimeoutMillis(
                 DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
@@ -188,7 +188,7 @@
 
     @Test
     public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         executeFadeOutAnimation();
 
         // Verify the end action after fade-out.
@@ -389,15 +389,15 @@
 
     @Test
     public void initializeA11yNode_showWindowModeButton_expectedValues() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
 
         mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
 
         assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
                 nodeInfo.getContentDescription());
-        assertEquals(mContext.getString(R.string.magnification_mode_switch_state_window),
-                nodeInfo.getStateDescription());
+        assertEquals(mContext.getString(R.string.magnification_mode_switch_state_full_screen),
+                nodeInfo.getStateDescription().toString());
         assertThat(nodeInfo.getActionList(),
                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
                         ACTION_CLICK.getId(), mContext.getResources().getString(
@@ -422,18 +422,18 @@
 
     @Test
     public void performClickA11yActions_showWindowModeButton_verifyTapAction() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         resetAndStubMockImageViewAndAnimator();
 
         mSpyImageView.performAccessibilityAction(
                 ACTION_CLICK.getId(), null);
 
-        verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
     }
 
     @Test
     public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         mSpyImageView.performAccessibilityAction(
                 R.id.accessibility_action_move_left, null);
@@ -456,7 +456,7 @@
 
     @Test
     public void showButton_hasAccessibilityWindowTitle() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         final WindowManager.LayoutParams layoutPrams =
                 mWindowManager.getLayoutParamsFromAttachedView();
@@ -468,7 +468,7 @@
 
     @Test
     public void showButton_registerComponentCallbacks() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index a56218b..82ae6ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -24,6 +24,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -46,6 +47,7 @@
     private FakeSwitchSupplier mSupplier;
     private MagnificationModeSwitch mModeSwitch;
     private ModeSwitchesController mModeSwitchesController;
+    private View mSpyView;
     @Mock
     private MagnificationModeSwitch.SwitchListener mListener;
 
@@ -57,6 +59,7 @@
         mModeSwitchesController = new ModeSwitchesController(mSupplier);
         mModeSwitchesController.setSwitchListenerDelegate(mListener);
         mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+        mSpyView = Mockito.spy(new View(mContext));
     }
 
     @After
@@ -94,12 +97,12 @@
     @Test
     public void testOnSwitchClick_showWindowModeButton_invokeListener() {
         mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
-        mModeSwitch.onSingleTap();
+        mModeSwitch.onSingleTap(mSpyView);
 
         verify(mListener).onSwitch(mContext.getDisplayId(),
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
     }
 
     private class FakeSwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 3d77d64..69ccc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -19,6 +19,8 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Choreographer.FrameCallback;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -46,6 +48,7 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
+import android.annotation.IdRes;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -65,6 +68,7 @@
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.IWindowSession;
+import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -126,8 +130,13 @@
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+
     private IWindowSession mWindowSessionSpy;
 
+    private View mSpyView;
+    private View.OnTouchListener mTouchListener;
+    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -165,6 +174,12 @@
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
+        mSpyView = Mockito.spy(new View(mContext));
+        doAnswer((invocation) -> {
+            mTouchListener = invocation.getArgument(0);
+            return null;
+        }).when(mSpyView).setOnTouchListener(
+                any(View.OnTouchListener.class));
     }
 
     @After
@@ -702,7 +717,7 @@
         });
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onSingleTap();
+            mWindowMagnificationController.onSingleTap(mSpyView);
         });
 
         final View mirrorView = mWindowManager.getAttachedView();
@@ -910,6 +925,38 @@
         assertTrue(magnificationCenterY.get() < bounds.bottom);
     }
 
+    @Test
+    public void performSingleTap_DragHandle() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                            1.5f, bounds.centerX(), bounds.centerY());
+                });
+        View dragButton = getInternalView(R.id.drag_handle);
+
+        // Perform a single-tap
+        final long downTime = SystemClock.uptimeMillis();
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+        verify(mWindowManager).addView(any(View.class), any());
+    }
+
+    private <T extends View> T getInternalView(@IdRes int idRes) {
+        View mirrorView = mWindowManager.getAttachedView();
+        T view = mirrorView.findViewById(idRes);
+        assertNotNull(view);
+        return view;
+    }
+
+    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+                                          float y) {
+        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+    }
+
     private CharSequence getAccessibilityWindowTitle() {
         final View mirrorView = mWindowManager.getAttachedView();
         if (mirrorView == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
new file mode 100644
index 0000000..2f94b69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.CompoundButton;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class WindowMagnificationSettingsTest extends SysuiTestCase {
+
+    private static final int MAGNIFICATION_SIZE_SMALL = 1;
+    private static final int MAGNIFICATION_SIZE_MEDIUM = 2;
+    private static final int MAGNIFICATION_SIZE_LARGE = 3;
+
+    private ViewGroup mSettingView;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+    @Mock
+    private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+    private TestableWindowManager mWindowManager;
+    private WindowMagnificationSettings mWindowMagnificationSettings;
+    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = getContext();
+        mContext.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        mWindowManager = spy(new TestableWindowManager(wm));
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+
+        mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
+                mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider);
+
+        mSettingView = mWindowMagnificationSettings.getSettingView();
+    }
+
+    @After
+    public void tearDown() {
+        mMotionEventHelper.recycleEvents();
+        mWindowMagnificationSettings.hideSettingPanel();
+    }
+
+    @Test
+    public void showSettingPanel_hasAccessibilityWindowTitle() {
+        mWindowMagnificationSettings.showSettingPanel();
+
+        final WindowManager.LayoutParams layoutPrams =
+                mWindowManager.getLayoutParamsFromAttachedView();
+        assertNotNull(layoutPrams);
+        assertEquals(getContext().getResources()
+                        .getString(com.android.internal.R.string.android_system_label),
+                layoutPrams.accessibilityTitle.toString());
+    }
+
+    @Test
+    public void performClick_smallSizeButton_changeMagnifierSizeSmall() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_small_button, MAGNIFICATION_SIZE_SMALL);
+    }
+
+    @Test
+    public void performClick_mediumSizeButton_changeMagnifierSizeMedium() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_medium_button, MAGNIFICATION_SIZE_MEDIUM);
+    }
+
+    @Test
+    public void performClick_largeSizeButton_changeMagnifierSizeLarge() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_large_button, MAGNIFICATION_SIZE_LARGE);
+    }
+
+    private void verifyOnSetMagnifierSize(@IdRes int viewId, int expectedSizeIndex) {
+        View changeSizeButton = getInternalView(viewId);
+
+        // Perform click
+        changeSizeButton.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onSetMagnifierSize(expectedSizeIndex);
+    }
+
+
+    @Test
+    public void performClick_fullScreenModeButton_setEditMagnifierSizeMode() {
+        View fullScreenModeButton = getInternalView(R.id.magnifier_full_button);
+        getInternalView(R.id.magnifier_panel_view);
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        fullScreenModeButton.performClick();
+
+        verify(mWindowManager).removeView(mSettingView);
+        verify(mWindowMagnificationSettingsCallback)
+                .onModeSwitch(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void performClick_editButton_setEditMagnifierSizeMode() {
+        View editButton = getInternalView(R.id.magnifier_edit_button);
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        editButton.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onEditMagnifierSizeMode(true);
+        verify(mWindowManager).removeView(mSettingView);
+    }
+
+    @Test
+    public void performClick_setDiagonalScrollingSwitch_toggleDiagonalScrollingSwitchMode() {
+        CompoundButton diagonalScrollingSwitch =
+                getInternalView(R.id.magnifier_horizontal_lock_switch);
+        final boolean currentCheckedState = diagonalScrollingSwitch.isChecked();
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        diagonalScrollingSwitch.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onSetDiagonalScrolling(!currentCheckedState);
+    }
+
+    private <T extends View> T getInternalView(@IdRes int idRes) {
+        T view = mSettingView.findViewById(idRes);
+        assertNotNull(view);
+        return view;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index ccf2f8b..e1bd25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -158,6 +158,18 @@
     }
 
     @Test
+    public void onModeSwitch_enabled_notifyCallback() throws RemoteException {
+        final int magnificationModeFullScreen = 1;
+        mCommandQueue.requestWindowMagnificationConnection(true);
+        waitForIdleSync();
+
+        mWindowMagnification.onModeSwitch(TEST_DISPLAY, magnificationModeFullScreen);
+
+        verify(mConnectionCallback).onChangeMagnificationMode(TEST_DISPLAY,
+                magnificationModeFullScreen);
+    }
+
+    @Test
     public void overviewProxyIsConnected_noController_resetFlag() {
         mOverviewProxyListener.onConnectionChanged(true);
 
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 d158892..e0d1f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -60,6 +60,9 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -154,7 +157,9 @@
     @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Captor
-    ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
+    ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
+    @Captor
+    ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
     @Captor
     ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
     @Captor
@@ -193,25 +198,38 @@
         when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
 
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
+        final List<ComponentInfoInternal> fpComponentInfo = List.of(
+                new ComponentInfoInternal("faceSensor" /* componentId */,
+                        "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+                        "00000001" /* serialNumber */, "" /* softwareVersion */));
+        final List<ComponentInfoInternal> faceComponentInfo = List.of(
+                new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+                        "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+                        "vendor/version/revision" /* softwareVersion */));
 
-        FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
-                1 /* sensorId */,
-                SensorProperties.STRENGTH_STRONG,
-                1 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequireHardwareAuthToken */);
-        List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        props.add(prop);
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+        final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
+                new FingerprintSensorPropertiesInternal(
+                        1 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        1 /* maxEnrollmentsPerUser */,
+                        fpComponentInfo,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                        true /* resetLockoutRequireHardwareAuthToken */));
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
+
+        final List<FaceSensorPropertiesInternal> faceProps = List.of(
+                new FaceSensorPropertiesInternal(
+                        2 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        1 /* maxEnrollmentsPerUser */,
+                        fpComponentInfo,
+                        FaceSensorProperties.TYPE_RGB,
+                        true /* supportsFaceDetection */,
+                        true /* supportsSelfIllumination */,
+                        true /* resetLockoutRequireHardwareAuthToken */));
+        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
@@ -219,12 +237,15 @@
 
         mAuthController.start();
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
 
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
 
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
 
         // Ensures that the operations posted on the handler get executed.
         mTestableLooper.processAllMessages();
@@ -237,6 +258,7 @@
             throws RemoteException {
         // This test is sensitive to prior FingerprintManager interactions.
         reset(mFingerprintManager);
+        reset(mFaceManager);
 
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -246,21 +268,27 @@
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager, never()).registerBiometricStateListener(any());
+        verify(mFaceManager, never()).registerBiometricStateListener(any());
 
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager).registerBiometricStateListener(any());
+        verify(mFaceManager).registerBiometricStateListener(any());
     }
 
     @Test
     public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
         // This test is sensitive to prior FingerprintManager interactions.
         reset(mFingerprintManager);
+        reset(mFaceManager);
 
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -270,18 +298,25 @@
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
 
         // Emulates a device with no authenticators (empty list).
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager).registerBiometricStateListener(
                 mBiometricStateCaptor.capture());
+        verify(mFaceManager).registerBiometricStateListener(
+                mBiometricStateCaptor.capture());
 
         // Enrollments changed for an unknown sensor.
-        mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */,
-                0xbeef /* sensorId */, true /* hasEnrollments */);
+        for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+            listener.onEnrollmentsChanged(0 /* userId */,
+                    0xbeef /* sensorId */, true /* hasEnrollments */);
+        }
         mTestableLooper.processAllMessages();
 
         // Nothing should crash.
@@ -827,4 +862,3 @@
         }
     }
 }
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a5..b33f9a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -412,7 +414,7 @@
 
         // WHEN enrollment changes to TRUE
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
-        mAuthControllerCallback.onEnrollmentsChanged();
+        mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT);
 
         // THEN mConfigured = TRUE
         assertTrue(triggerSensor.mConfigured);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 24d0515..5ec6bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -219,7 +221,7 @@
         Pair<Float, PointF> udfps = setupUdfps();
 
         // WHEN all authenticators are registered
-        mAuthControllerCallback.onAllAuthenticatorsRegistered();
+        mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
         mDelayableExecutor.runAllReady();
 
         // THEN lock icon view location is updated with the same coordinates as auth controller vals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 6fff440..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,60 +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.keyguard.data.repository
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
-
-    private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
-    val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
-
-    var onClickedResult: OnClickedResult = OnClickedResult.Handled
-
-    private val _state =
-        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
-            KeyguardQuickAffordanceConfig.State.Hidden
-        )
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
-
-    override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
-    ): OnClickedResult {
-        _onClickedInvocations.add(animationController)
-        return onClickedResult
-    }
-
-    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
-        _state.value = state
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
deleted file mode 100644
index a24fc93..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
+++ /dev/null
@@ -1,44 +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.keyguard.data.repository
-
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
-
-/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
-class FakeKeyguardQuickAffordanceConfigs(
-    private val configsByPosition:
-        Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceConfigs {
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition
-    ): List<KeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        configClass: KClass<out KeyguardQuickAffordanceConfig>
-    ): KeyguardQuickAffordanceConfig {
-        return configsByPosition.values
-            .flatten()
-            .associateBy { config -> config::class }
-            .getValue(configClass)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 10d2e4d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,55 +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.keyguard.data.repository
-
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
-class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
-
-    private val modelByPosition =
-        mutableMapOf<
-            KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
-    init {
-        KeyguardQuickAffordancePosition.values().forEach { value ->
-            modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
-        }
-    }
-
-    override fun affordance(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return modelByPosition.getValue(position)
-    }
-
-    suspend fun setModel(
-        position: KeyguardQuickAffordancePosition,
-        model: KeyguardQuickAffordanceModel
-    ) {
-        modelByPosition.getValue(position).value = model
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index d40b985..11eb4e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -43,11 +43,6 @@
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
-    init {
-        setDozeAmount(0f)
-        setDozing(false)
-    }
-
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
deleted file mode 100644
index dc0e6f7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
+++ /dev/null
@@ -1,193 +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.keyguard.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlin.reflect.KClass
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
-
-    private lateinit var underTest: KeyguardQuickAffordanceRepository
-
-    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
-    private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
-    private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
-        quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
-        qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
-
-        underTest =
-            KeyguardQuickAffordanceRepositoryImpl(
-                configs =
-                    FakeKeyguardQuickAffordanceConfigs(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
-            )
-    }
-
-    @Test
-    fun `bottom start affordance - none`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom start affordance - home controls`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val state =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        homeControls.setState(state)
-
-        assertThat(latest).isEqualTo(state.toModel(homeControls::class))
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - none`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - quick access wallet`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val quickAccessWalletState =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAccessWallet.setState(quickAccessWalletState)
-        val qrCodeScannerState =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        qrCodeScanner.setState(qrCodeScannerState)
-
-        assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
-        job.cancel()
-    }
-
-    @Test
-    fun `bottom end affordance - qr code scanner`() = runBlockingTest {
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest
-                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        val state =
-            KeyguardQuickAffordanceConfig.State.Visible(
-                icon = mock(),
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        qrCodeScanner.setState(state)
-
-        assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
-        job.cancel()
-    }
-
-    private fun KeyguardQuickAffordanceConfig.State?.toModel(
-        configKey: KClass<out KeyguardQuickAffordanceConfig>,
-    ): KeyguardQuickAffordanceModel? {
-        return when (this) {
-            is KeyguardQuickAffordanceConfig.State.Visible ->
-                KeyguardQuickAffordanceModel.Visible(
-                    configKey = configKey,
-                    icon = icon,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-                )
-            is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
-            null -> null
-        }
-    }
-
-    companion object {
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 3d2c51a..3aa2266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6ea1daa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+    var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+    private val _state =
+        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+            KeyguardQuickAffordanceConfig.State.Hidden
+        )
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): OnClickedResult {
+        return onClickedResult
+    }
+
+    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+        _state.value = state
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
new file mode 100644
index 0000000..e68c43f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
+class FakeKeyguardQuickAffordanceRegistry(
+    private val configsByPosition:
+        Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
+
+    override fun getAll(
+        position: KeyguardQuickAffordancePosition
+    ): List<FakeKeyguardQuickAffordanceConfig> {
+        return configsByPosition.getValue(position)
+    }
+
+    override fun get(
+        configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+    ): FakeKeyguardQuickAffordanceConfig {
+        return configsByPosition.values
+            .flatten()
+            .associateBy { config -> config::class }
+            .getValue(configClass)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 810c6dc..9acd21c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,20 +1,21 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ *  Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index ef588f5..059487d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -23,7 +23,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 6fd04de..d4fba41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -15,12 +15,12 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 345c51f..5a3a78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsResponse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index ba0c31f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,47 +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.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
-
-/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
-class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
-
-    data class Invocation(
-        val intent: Intent,
-        val canShowWhileLocked: Boolean,
-        val animationController: ActivityLaunchAnimator.Controller?
-    )
-
-    private val _invocations = mutableListOf<Invocation>()
-    val invocations: List<Invocation> = _invocations
-
-    override fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?
-    ) {
-        _invocations.add(
-            Invocation(
-                intent = intent,
-                canShowWhileLocked = canShowWhileLocked,
-                animationController = animationController,
-            )
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
new file mode 100644
index 0000000..c5e828e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
+
+    companion object {
+        private val INTENT = Intent("some.intent.action")
+        private val DRAWABLE = mock<ContainedDrawable>()
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+        @Parameters(
+            name =
+                "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+                    " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+        )
+        @JvmStatic
+        fun data() =
+            listOf(
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+            )
+    }
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+    private lateinit var underTest: KeyguardQuickAffordanceInteractor
+
+    @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+    @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+    @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+    @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+    @JvmField @Parameter(4) var startActivity: Boolean = false
+    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+        underTest =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
+                registry =
+                    FakeKeyguardQuickAffordanceRegistry(
+                        mapOf(
+                            KeyguardQuickAffordancePosition.BOTTOM_START to
+                                listOf(
+                                    homeControls,
+                                ),
+                            KeyguardQuickAffordancePosition.BOTTOM_END to
+                                listOf(
+                                    object : FakeKeyguardQuickAffordanceConfig() {},
+                                    object : FakeKeyguardQuickAffordanceConfig() {},
+                                ),
+                        ),
+                    ),
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+            )
+    }
+
+    @Test
+    fun onQuickAffordanceClicked() = runBlockingTest {
+        setUpMocks(
+            needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+            keyguardIsUnlocked = keyguardIsUnlocked,
+        )
+
+        homeControls.setState(
+            state =
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = DRAWABLE,
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+        )
+        homeControls.onClickedResult =
+            if (startActivity) {
+                KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                    intent = INTENT,
+                    canShowWhileLocked = canShowWhileLocked,
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+            }
+
+        underTest.onQuickAffordanceClicked(
+            configKey = homeControls::class,
+            animationController = animationController,
+        )
+
+        if (startActivity) {
+            if (needsToUnlockFirst) {
+                verify(activityStarter)
+                    .postStartActivityDismissingKeyguard(
+                        any(),
+                        /* delay= */ eq(0),
+                        same(animationController),
+                    )
+            } else {
+                verify(activityStarter)
+                    .startActivity(
+                        any(),
+                        /* dismissShade= */ eq(true),
+                        same(animationController),
+                        /* showOverLockscreenWhenLocked= */ eq(true),
+                    )
+            }
+        } else {
+            verifyZeroInteractions(activityStarter)
+        }
+    }
+
+    private fun setUpMocks(
+        needStrongAuthAfterBoot: Boolean = true,
+        keyguardIsUnlocked: Boolean = false,
+    ) {
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(any()))
+            .thenReturn(
+                if (needStrongAuthAfterBoot) {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                } else {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+                }
+            )
+        whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
new file mode 100644
index 0000000..d3fc29f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: KeyguardQuickAffordanceInteractor
+
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        repository = FakeKeyguardRepository()
+        repository.setKeyguardShowing(true)
+
+        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+        underTest =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = KeyguardInteractor(repository = repository),
+                registry =
+                    FakeKeyguardQuickAffordanceRegistry(
+                        mapOf(
+                            KeyguardQuickAffordancePosition.BOTTOM_START to
+                                listOf(
+                                    homeControls,
+                                ),
+                            KeyguardQuickAffordancePosition.BOTTOM_END to
+                                listOf(
+                                    quickAccessWallet,
+                                    qrCodeScanner,
+                                ),
+                        ),
+                    ),
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+            )
+    }
+
+    @Test
+    fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
+        val configKey = homeControls::class
+        homeControls.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+        assertThat(visibleModel.configKey).isEqualTo(configKey)
+        assertThat(visibleModel.icon).isEqualTo(ICON)
+        assertThat(visibleModel.contentDescriptionResourceId)
+            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        job.cancel()
+    }
+
+    @Test
+    fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
+        val configKey = quickAccessWallet::class
+        quickAccessWallet.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+        assertThat(visibleModel.configKey).isEqualTo(configKey)
+        assertThat(visibleModel.icon).isEqualTo(ICON)
+        assertThat(visibleModel.contentDescriptionResourceId)
+            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        job.cancel()
+    }
+
+    @Test
+    fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
+        repository.setDozing(true)
+        homeControls.setState(
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
+        runBlockingTest {
+            repository.setKeyguardShowing(false)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = ICON,
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+            )
+
+            var latest: KeyguardQuickAffordanceModel? = null
+            val job =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { latest = it }
+                    .launchIn(this)
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+            job.cancel()
+        }
+
+    companion object {
+        private val ICON: ContainedDrawable = mock()
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
deleted file mode 100644
index b3c1ae0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
+++ /dev/null
@@ -1,178 +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.keyguard.domain.usecase
-
-import android.content.Intent
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(Parameterized::class)
-class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
-
-    companion object {
-        private val INTENT = Intent("some.intent.action")
-
-        @Parameters(
-            name =
-                "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
-                    " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
-        )
-        @JvmStatic
-        fun data() =
-            listOf(
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ true,
-                ),
-            )
-    }
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
-
-    private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
-
-    @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
-    @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
-    @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
-    @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest =
-            LaunchKeyguardQuickAffordanceUseCaseImpl(
-                lockPatternUtils = lockPatternUtils,
-                keyguardStateController = keyguardStateController,
-                userTracker = userTracker,
-                activityStarter = activityStarter,
-            )
-    }
-
-    @Test
-    fun invoke() {
-        setUpMocks(
-            needStrongAuthAfterBoot = needStrongAuthAfterBoot,
-            keyguardIsUnlocked = keyguardIsUnlocked,
-        )
-
-        underTest(
-            intent = INTENT,
-            canShowWhileLocked = canShowWhileLocked,
-            animationController = animationController,
-        )
-
-        if (needsToUnlockFirst) {
-            verify(activityStarter)
-                .postStartActivityDismissingKeyguard(
-                    INTENT,
-                    /* delay= */ 0,
-                    animationController,
-                )
-        } else {
-            verify(activityStarter)
-                .startActivity(
-                    INTENT,
-                    /* dismissShade= */ true,
-                    animationController,
-                    /* showOverLockscreenWhenLocked= */ true,
-                )
-        }
-    }
-
-    private fun setUpMocks(
-        needStrongAuthAfterBoot: Boolean = true,
-        keyguardIsUnlocked: Boolean = false,
-    ) {
-        whenever(userTracker.userHandle).thenReturn(mock())
-        whenever(lockPatternUtils.getStrongAuthForUser(any()))
-            .thenReturn(
-                if (needStrongAuthAfterBoot) {
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-                } else {
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
-                }
-            )
-        whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
deleted file mode 100644
index b90400be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
+++ /dev/null
@@ -1,161 +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.keyguard.domain.usecase
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
-
-    private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
-
-    private lateinit var repository: FakeKeyguardRepository
-    private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
-    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
-
-    @Before
-    fun setUp() {
-        repository = FakeKeyguardRepository()
-        repository.setKeyguardShowing(true)
-        isDozingUseCase = ObserveIsDozingUseCase(repository)
-        isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
-        quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
-
-        underTest =
-            ObserveKeyguardQuickAffordanceUseCase(
-                repository = quickAffordanceRepository,
-                isDozingUseCase = isDozingUseCase,
-                isKeyguardShowingUseCase = isKeyguardShowingUseCase,
-            )
-    }
-
-    @Test
-    fun `invoke - affordance is visible`() = runBlockingTest {
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
-        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
-        assertThat(visibleModel.configKey).isEqualTo(configKey)
-        assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance not visible while dozing`() = runBlockingTest {
-        repository.setDozing(true)
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
-        repository.setKeyguardShowing(false)
-        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
-        val model =
-            KeyguardQuickAffordanceModel.Visible(
-                configKey = configKey,
-                icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-            )
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_END,
-            model,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    @Test
-    fun `invoke - affordance is none`() = runBlockingTest {
-        quickAffordanceRepository.setModel(
-            KeyguardQuickAffordancePosition.BOTTOM_START,
-            KeyguardQuickAffordanceModel.Hidden,
-        )
-
-        var latest: KeyguardQuickAffordanceModel? = null
-        val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
-                .onEach { latest = it }
-                .launchIn(this)
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        job.cancel()
-    }
-
-    companion object {
-        private val ICON: ContainedDrawable = mock()
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 00dd58e..19491f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -18,26 +18,22 @@
 
 import android.content.Intent
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.containeddrawable.ContainedDrawable
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsKeyguardShowingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -45,12 +41,15 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -60,14 +59,15 @@
 
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     private lateinit var underTest: KeyguardBottomAreaViewModel
 
-    private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
     private lateinit var repository: FakeKeyguardRepository
-    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
-    private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
+    private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -78,161 +78,53 @@
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
-        affordanceRepository = FakeKeyguardQuickAffordanceRepository()
-        repository = FakeKeyguardRepository()
-        isDozingUseCase =
-            ObserveIsDozingUseCase(
-                repository = repository,
-            )
-        isKeyguardShowingUseCase =
-            ObserveIsKeyguardShowingUseCase(
-                repository = repository,
-            )
-        launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
         homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
         quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
         qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        registry =
+            FakeKeyguardQuickAffordanceRegistry(
+                mapOf(
+                    KeyguardQuickAffordancePosition.BOTTOM_START to
+                        listOf(
+                            homeControlsQuickAffordanceConfig,
+                        ),
+                    KeyguardQuickAffordancePosition.BOTTOM_END to
+                        listOf(
+                            quickAccessWalletAffordanceConfig,
+                            qrCodeScannerAffordanceConfig,
+                        ),
+                ),
+            )
+        repository = FakeKeyguardRepository()
 
+        val keyguardInteractor = KeyguardInteractor(repository = repository)
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
         underTest =
             KeyguardBottomAreaViewModel(
-                observeQuickAffordanceUseCase =
-                    ObserveKeyguardQuickAffordanceUseCase(
-                        repository = affordanceRepository,
-                        isDozingUseCase = isDozingUseCase,
-                        isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+                keyguardInteractor = keyguardInteractor,
+                quickAffordanceInteractor =
+                    KeyguardQuickAffordanceInteractor(
+                        keyguardInteractor = keyguardInteractor,
+                        registry = registry,
+                        lockPatternUtils = lockPatternUtils,
+                        keyguardStateController = keyguardStateController,
+                        userTracker = userTracker,
+                        activityStarter = activityStarter,
                     ),
-                onQuickAffordanceClickedUseCase =
-                    OnKeyguardQuickAffordanceClickedUseCase(
-                        configs =
-                            FakeKeyguardQuickAffordanceConfigs(
-                                mapOf(
-                                    KeyguardQuickAffordancePosition.BOTTOM_START to
-                                        listOf(
-                                            homeControlsQuickAffordanceConfig,
-                                        ),
-                                    KeyguardQuickAffordancePosition.BOTTOM_END to
-                                        listOf(
-                                            quickAccessWalletAffordanceConfig,
-                                            qrCodeScannerAffordanceConfig,
-                                        ),
-                                ),
-                            ),
-                        launchAffordanceUseCase = launchQuickAffordanceUseCase,
-                    ),
-                observeBottomAreaAlphaUseCase =
-                    ObserveBottomAreaAlphaUseCase(
-                        repository = repository,
-                    ),
-                observeIsDozingUseCase = isDozingUseCase,
-                observeAnimateBottomAreaTransitionsUseCase =
-                    ObserveAnimateBottomAreaTransitionsUseCase(
-                        repository = repository,
-                    ),
-                observeDozeAmountUseCase =
-                    ObserveDozeAmountUseCase(
-                        repository = repository,
-                    ),
-                observeClockPositionUseCase =
-                    ObserveClockPositionUseCase(
-                        repository = repository,
-                    ),
+                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
             )
     }
 
     @Test
-    fun `startButton - present - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val testConfig =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent = Intent("action"),
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = testConfig,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = testConfig,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `endButton - present - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.endButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val config =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent =
-                        null, // This will cause it to tell the system that the click was handled.
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = config,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `startButton - not present - not dozing - lockscreen showing - model is none`() =
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
-            repository.setDozing(false)
-            repository.setKeyguardShowing(true)
-            val config =
-                TestConfig(
-                    isVisible = false,
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = config,
-                configKey = configKey,
-            )
-            job.cancel()
-        }
-
-    @Test
-    fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest {
+    fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
         var latest: KeyguardQuickAffordanceViewModel? = null
         val job = underTest.startButton.onEach { latest = it }.launchIn(this)
 
-        repository.setDozing(true)
-        repository.setKeyguardShowing(true)
-        val config =
+        val testConfig =
             TestConfig(
                 isVisible = true,
                 icon = mock(),
@@ -242,53 +134,90 @@
         val configKey =
             setUpQuickAffordanceModel(
                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig = config,
+                testConfig = testConfig,
             )
 
         assertQuickAffordanceViewModel(
             viewModel = latest,
-            testConfig = TestConfig(isVisible = false),
+            testConfig = testConfig,
             configKey = configKey,
         )
         job.cancel()
     }
 
     @Test
-    fun `startButton - present - not dozing - lockscreen not showing - model is none`() =
-        runBlockingTest {
-            var latest: KeyguardQuickAffordanceViewModel? = null
-            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+    fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.endButton.onEach { latest = it }.launchIn(this)
 
-            repository.setDozing(false)
-            repository.setKeyguardShowing(false)
-            val config =
-                TestConfig(
-                    isVisible = true,
-                    icon = mock(),
-                    canShowWhileLocked = false,
-                    intent = Intent("action"),
-                )
-            val configKey =
-                setUpQuickAffordanceModel(
-                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                    testConfig = config,
-                )
-
-            assertQuickAffordanceViewModel(
-                viewModel = latest,
-                testConfig = TestConfig(isVisible = false),
-                configKey = configKey,
+        val config =
+            TestConfig(
+                isVisible = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = null, // This will cause it to tell the system that the click was handled.
             )
-            job.cancel()
-        }
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = config,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `startButton - not present - model is hidden`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        val config =
+            TestConfig(
+                isVisible = false,
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = config,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
 
     @Test
     fun animateButtonReveal() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_START,
+            testConfig = testConfig,
+        )
+
         val values = mutableListOf<Boolean>()
-        val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+        val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
 
         repository.setAnimateDozingTransitions(true)
+        yield()
         repository.setAnimateDozingTransitions(false)
+        yield()
 
         assertThat(values).isEqualTo(listOf(false, true, false))
         job.cancel()
@@ -413,7 +342,7 @@
         job.cancel()
     }
 
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+    private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
         repository.setDozeAmount(dozeAmount)
         return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
     }
@@ -421,40 +350,37 @@
     private suspend fun setUpQuickAffordanceModel(
         position: KeyguardQuickAffordancePosition,
         testConfig: TestConfig,
-    ): KClass<*> {
+    ): KClass<out FakeKeyguardQuickAffordanceConfig> {
         val config =
             when (position) {
                 KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
                 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
             }
 
-        affordanceRepository.setModel(
-            position = position,
-            model =
-                if (testConfig.isVisible) {
-                    if (testConfig.intent != null) {
-                        config.onClickedResult =
-                            KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
-                                intent = testConfig.intent,
-                                canShowWhileLocked = testConfig.canShowWhileLocked,
-                            )
-                    }
-                    KeyguardQuickAffordanceModel.Visible(
-                        configKey = config::class,
-                        icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
-                        contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
-                    )
-                } else {
-                    KeyguardQuickAffordanceModel.Hidden
+        val state =
+            if (testConfig.isVisible) {
+                if (testConfig.intent != null) {
+                    config.onClickedResult =
+                        KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                            intent = testConfig.intent,
+                            canShowWhileLocked = testConfig.canShowWhileLocked,
+                        )
                 }
-        )
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.State.Hidden
+            }
+        config.setState(state)
         return config::class
     }
 
     private fun assertQuickAffordanceViewModel(
         viewModel: KeyguardQuickAffordanceViewModel?,
         testConfig: TestConfig,
-        configKey: KClass<*>,
+        configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
     ) {
         checkNotNull(viewModel)
         assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
@@ -466,19 +392,11 @@
                     animationController = animationController,
                 )
             )
-            testConfig.intent?.let { intent ->
-                assertThat(launchQuickAffordanceUseCase.invocations)
-                    .isEqualTo(
-                        listOf(
-                            FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
-                                intent = intent,
-                                canShowWhileLocked = testConfig.canShowWhileLocked,
-                                animationController = animationController,
-                            )
-                        )
-                    )
+            if (testConfig.intent != null) {
+                assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
+            } else {
+                verifyZeroInteractions(activityStarter)
             }
-                ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
         } else {
             assertThat(viewModel.isVisible).isFalse()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 533c231..314997d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -43,7 +43,6 @@
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.filters.SmallTest;
 
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -54,7 +53,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -83,8 +82,7 @@
             LocalBluetoothLeBroadcast.class);
     private ActivityStarter mStarter = mock(ActivityStarter.class);
     private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
-    private NotificationEntryManager mNotificationEntryManager =
-            mock(NotificationEntryManager.class);
+    private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -120,7 +118,7 @@
 
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotificationEntryManager, mDialogLaunchAnimator,
+                mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 6afed1a..4779d32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -47,7 +47,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
 import org.junit.After;
 import org.junit.Before;
@@ -78,8 +78,7 @@
     private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
     private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
     private final MediaDevice mMediaDevice = mock(MediaDevice.class);
-    private final NotificationEntryManager mNotificationEntryManager =
-            mock(NotificationEntryManager.class);
+    private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -104,7 +103,7 @@
 
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
-                mNotificationEntryManager, mDialogLaunchAnimator,
+                mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index fba1986..bd913ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -63,7 +63,6 @@
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.people.widget.PeopleTileKey;
 import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
@@ -196,8 +195,6 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private NotificationEntryManager mNotificationEntryManager;
-    @Mock
     private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
     @Mock
     private BackupManager mBackupManager;
@@ -234,8 +231,6 @@
         when(mMockContext.getString(R.string.over_two_weeks_timestamp)).thenReturn(
                 mContext.getString(R.string.over_two_weeks_timestamp));
         when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
-        when(mNotificationEntryManager.getVisibleNotifications())
-                .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 642e29b..2ba8782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -22,13 +22,17 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -42,47 +46,38 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
 class FooterActionsControllerTest : LeakCheckedTest() {
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock
-    private lateinit var userInfoController: UserInfoController
-    @Mock
-    private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
-    @Mock
-    private lateinit var multiUserSwitchController: MultiUserSwitchController
-    @Mock
-    private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
-    @Mock
-    private lateinit var globalActionsDialog: GlobalActionsDialogLite
-    @Mock
-    private lateinit var uiEventLogger: UiEventLogger
-    @Mock
-    private lateinit var securityFooterController: QSSecurityFooter
-    @Mock
-    private lateinit var fgsManagerController: QSFgsManagerFooter
+
+    @get:Rule var expect: Expect = Expect.create()
+
+    @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var userInfoController: UserInfoController
+    @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
+    @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
+    @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
+    @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var securityFooterController: QSSecurityFooter
+    @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
     @Captor
     private lateinit var visibilityChangedCaptor:
         ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
 
     private lateinit var controller: FooterActionsController
 
+    private val configurationController = FakeConfigurationController()
     private val metricsLogger: MetricsLogger = FakeMetricsLogger()
-    private lateinit var view: FooterActionsView
     private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+    private lateinit var view: FooterActionsView
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeSettings: FakeSettings
     private lateinit var securityFooter: View
@@ -90,12 +85,15 @@
 
     @Before
     fun setUp() {
+        // We want to make sure testable resources are always used
+        context.ensureTestableResources()
+
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         fakeSettings = FakeSettings()
 
         whenever(multiUserSwitchControllerFactory.create(any()))
-                .thenReturn(multiUserSwitchController)
+            .thenReturn(multiUserSwitchController)
         whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
 
         securityFooter = View(mContext)
@@ -135,7 +133,7 @@
         view.findViewById<View>(R.id.pm_lite).performClick()
         // Verify clicks are logged
         verify(uiEventLogger, Mockito.times(1))
-                .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+            .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
     }
 
     @Test
@@ -299,6 +297,86 @@
         assertThat(booleanCaptor.allValues.last()).isTrue()
     }
 
+    @Test
+    fun setExpansion_inSplitShade_alphaFollowsExpansion() {
+        enableSplitShade()
+
+        controller.setExpansion(0f)
+        expect.that(view.alpha).isEqualTo(0f)
+
+        controller.setExpansion(0.25f)
+        expect.that(view.alpha).isEqualTo(0.25f)
+
+        controller.setExpansion(0.5f)
+        expect.that(view.alpha).isEqualTo(0.5f)
+
+        controller.setExpansion(0.75f)
+        expect.that(view.alpha).isEqualTo(0.75f)
+
+        controller.setExpansion(1f)
+        expect.that(view.alpha).isEqualTo(1f)
+    }
+
+    @Test
+    fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
+        enableSplitShade()
+
+        controller.setExpansion(0f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+        controller.setExpansion(0.5f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+        controller.setExpansion(0.9f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+        controller.setExpansion(0.91f)
+        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+        controller.setExpansion(0.95f)
+        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+        controller.setExpansion(1f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+    }
+
+    @Test
+    fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
+        disableSplitShade()
+
+        controller.setExpansion(0f)
+        expect.that(view.alpha).isEqualTo(0f)
+
+        controller.setExpansion(0.5f)
+        expect.that(view.alpha).isEqualTo(0f)
+
+        controller.setExpansion(0.9f)
+        expect.that(view.alpha).isEqualTo(0f)
+
+        controller.setExpansion(0.91f)
+        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+        controller.setExpansion(0.95f)
+        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+        controller.setExpansion(1f)
+        expect.that(view.alpha).isEqualTo(1f)
+    }
+
+    @Test
+    fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
+        disableSplitShade()
+
+        controller.setExpansion(0f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+        controller.setExpansion(0.5f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+        controller.setExpansion(1f)
+        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+    }
+
     private fun setVisibilities(
         securityFooterVisible: Boolean,
         fgsFooterVisible: Boolean,
@@ -311,15 +389,52 @@
     }
 
     private fun inflateView(): FooterActionsView {
-        return LayoutInflater.from(context)
-                .inflate(R.layout.footer_actions, null) as FooterActionsView
+        return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
+            as FooterActionsView
     }
 
     private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
-        return FooterActionsController(view, multiUserSwitchControllerFactory,
-                activityStarter, userManager, userTracker, userInfoController,
-                deviceProvisionedController, securityFooterController, fgsManagerController,
-                falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
-                showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
+        return FooterActionsController(
+            view,
+            multiUserSwitchControllerFactory,
+            activityStarter,
+            userManager,
+            userTracker,
+            userInfoController,
+            deviceProvisionedController,
+            securityFooterController,
+            fgsManagerController,
+            falsingManager,
+            metricsLogger,
+            globalActionsDialogProvider,
+            uiEventLogger,
+            showPMLiteButton = true,
+            fakeSettings,
+            Handler(testableLooper.looper),
+            configurationController)
     }
-}
\ No newline at end of file
+
+    private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        configurationController.notifyConfigurationChanged()
+    }
+}
+
+private const val FLOAT_TOLERANCE = 0.01f
+
+private val View.backgroundAlphaFraction: Float?
+    get() {
+        return if (background != null) {
+            background.alpha / 255f
+        } else {
+            null
+        }
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 32c66d2..10f6ce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -205,6 +205,40 @@
     }
 
     @Test
+    public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
+        // Random test values without any meaning. They just have to be different from each other.
+        float expansion = 0.123f;
+        float panelExpansionFraction = 0.321f;
+        float proposedTranslation = 456f;
+        float squishinessFraction = 0.987f;
+
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+
+        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+                squishinessFraction);
+
+        verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
+    }
+
+    @Test
+    public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() {
+        // Random test values without any meaning. They just have to be different from each other.
+        float expansion = 0.123f;
+        float panelExpansionFraction = 0.321f;
+        float proposedTranslation = 456f;
+        float squishinessFraction = 0.987f;
+
+        QSFragment fragment = resumeAndGetFragment();
+        disableSplitShade();
+
+        fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+                squishinessFraction);
+
+        verify(mQSFooterActionController).setExpansion(expansion);
+    }
+
+    @Test
     public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
         QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
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 002f23a..024d3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,8 +23,12 @@
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
 import android.net.Uri
-import android.view.WindowManager
-import android.view.WindowManager.ScreenshotSource
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+
 import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
 import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
@@ -43,13 +47,12 @@
 
     @Test
     fun testFullScreenshot() {
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
         val onSavedListener = mock<Consumer<Uri>>()
         val callback = mock<RequestCallback>()
         val processor = RequestProcessor(controller)
 
-        processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
-            request, callback)
+        processor.processRequest(request, onSavedListener, callback)
 
         verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
             eq(onSavedListener), eq(callback))
@@ -57,13 +60,12 @@
 
     @Test
     fun testSelectedRegionScreenshot() {
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
         val onSavedListener = mock<Consumer<Uri>>()
         val callback = mock<RequestCallback>()
         val processor = RequestProcessor(controller)
 
-        processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
-            request, callback)
+        processor.processRequest(request, onSavedListener, callback)
 
         verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
             eq(onSavedListener), eq(callback))
@@ -82,14 +84,13 @@
         val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
         val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
-            bounds, Insets.NONE, taskId, userId, topComponent)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+            bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent)
 
         val onSavedListener = mock<Consumer<Uri>>()
         val callback = mock<RequestCallback>()
 
-        processor.processRequest(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
-            request, callback)
+        processor.processRequest(request, onSavedListener, callback)
 
         verify(controller).handleImageAsScreenshot(
             bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
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 fc28349..3f4e2a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -98,9 +98,7 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
@@ -379,10 +377,7 @@
     @Mock
     private ViewTreeObserver mViewTreeObserver;
     @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
-    @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
-    @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
-    @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+    @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
@@ -577,9 +572,7 @@
                 mSystemClock,
                 mock(CameraGestureHelper.class),
                 () -> mKeyguardBottomAreaViewModel,
-                () -> mSetClockPositionUseCase,
-                () -> mSetKeyguardBottomAreaAlphaUseCase,
-                () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
+                () -> mKeyguardBottomAreaInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 () -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 65d0adc..c6207e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
 import android.app.Notification;
@@ -216,41 +215,4 @@
         // The entry has just been added so we should not remove immediately.
         assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.getKey()));
     }
-
-    @Test
-    public void testShouldExtendLifetime() {
-        mAlertingNotificationManager.showNotification(mEntry);
-
-        // While the entry is alerting, it should not be removable.
-        assertTrue(mAlertingNotificationManager.shouldExtendLifetime(mEntry));
-    }
-
-    @Test
-    public void testSetShouldManageLifetime_setShouldManage() {
-        mAlertingNotificationManager.showNotification(mEntry);
-
-        mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
-
-        assertTrue(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
-    }
-
-    @Test
-    public void testSetShouldManageLifetime_setShouldManageCallsRemoval() {
-        mAlertingNotificationManager.showNotification(mEntry);
-        mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
-        if (mAlertingNotificationManager instanceof TestableAlertingNotificationManager) {
-            TestableAlertingNotificationManager testableManager =
-                    (TestableAlertingNotificationManager) mAlertingNotificationManager;
-            verify(testableManager.mLastCreatedEntry).removeAsSoonAsPossible();
-        }
-    }
-
-    @Test
-    public void testSetShouldManageLifetime_setShouldNotManage() {
-        mAlertingNotificationManager.mExtendedLifetimeAlertEntries.add(mEntry);
-
-        mAlertingNotificationManager.setShouldManageLifetime(mEntry, false /* shouldManage */);
-
-        assertFalse(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
-    }
 }
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 c5beee8..22d61a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
 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;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -50,7 +51,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.IActivityManager;
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyResourcesManager;
@@ -161,8 +161,6 @@
     @Mock
     private LockPatternUtils mLockPatternUtils;
     @Mock
-    private IActivityManager mIActivityManager;
-    @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private AccessibilityManager mAccessibilityManager;
@@ -256,8 +254,7 @@
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
                 mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
-                mScreenLifecycle, mIActivityManager, mKeyguardBypassController,
-                mAccessibilityManager);
+                mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager);
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -974,11 +971,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN 'face unlocked. press unlock icon to open' message shows
-        String pressToOpen = mContext.getString(R.string.keyguard_face_successful_unlock_press);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
-
-        assertThat(mTextView.getText()).isNotEqualTo(pressToOpen);
+        // THEN 'face unlocked' then 'press unlock icon to open' message show
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
     }
 
 
@@ -999,10 +996,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'face unlocked. swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
@@ -1021,10 +1019,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
@@ -1042,10 +1041,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 34d13c7..6e29669 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -161,10 +161,8 @@
                     smartReplyController,
                     visibilityProvider,
                     notificationEntryManager,
-                    rebuilder,
                     centralSurfacesOptionalLazy,
                     statusBarStateController,
-                    mainHandler,
                     remoteInputUriController,
                     clickNotifier,
                     actionClickLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
deleted file mode 100644
index 842f057..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.ArraySet;
-
-import androidx.annotation.NonNull;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-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.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Unit tests for {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class NotificationEntryManagerTest extends SysuiTestCase {
-    private static final String TEST_PACKAGE_NAME = "test";
-    private static final int TEST_UID = 0;
-
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private KeyguardEnvironment mEnvironment;
-    @Mock private ExpandableNotificationRow mRow;
-    @Mock private NotificationEntryListener mEntryListener;
-    @Mock private NotifCollectionListener mNotifCollectionListener;
-    @Mock private NotificationRemoveInterceptor mRemoveInterceptor;
-    @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private RankingMap mRankingMap;
-    @Mock private NotificationGroupManagerLegacy mGroupManager;
-    @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private RowInflaterTask mAsyncInflationTask;
-    @Mock private NotificationEntryManagerLogger mLogger;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock private LeakDetector mLeakDetector;
-    @Mock private NotificationMediaManager mNotificationMediaManager;
-    @Mock private NotificationRowBinder mNotificationRowBinder;
-    @Mock private NotificationListener mNotificationListener;
-    @Mock private IStatusBarService mStatusBarService;
-
-    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
-
-    private int mId;
-    private NotificationEntry mEntry;
-    private DismissedByUserStats mStats;
-    private StatusBarNotification mSbn;
-    private NotificationEntryManager mEntryManager;
-
-    private void setUserSentiment(String key, int sentiment) {
-        doAnswer(invocationOnMock -> {
-            Ranking ranking = (Ranking)
-                    invocationOnMock.getArguments()[1];
-            ranking.populate(
-                    key,
-                    0,
-                    false,
-                    0,
-                    0,
-                    IMPORTANCE_DEFAULT,
-                    null, null,
-                    null, null, null, true, sentiment, false, -1, false, null, null, false, false,
-                    false, null, 0, false);
-            return true;
-        }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
-    }
-
-    private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
-        doAnswer(invocationOnMock -> {
-            Ranking ranking = (Ranking)
-                    invocationOnMock.getArguments()[1];
-            ranking.populate(
-                    key,
-                    0,
-                    false,
-                    0,
-                    0,
-                    IMPORTANCE_DEFAULT,
-                    null, null,
-                    null, null, null, true,
-                    Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, smartActions, null, false, false, false, null, 0, false);
-            return true;
-        }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
-    }
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(SmartReplyController.class);
-
-        allowTestableLooperAsMainThread();
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                Handler.createAsync(TestableLooper.get(this).getLooper()));
-
-        mEntry = createNotification();
-        mStats = defaultStats(mEntry);
-        mSbn = mEntry.getSbn();
-
-        mEntryManager = new NotificationEntryManager(
-                mLogger,
-                mGroupManager,
-                mNotifPipelineFlags,
-                () -> mNotificationRowBinder,
-                () -> mRemoteInputManager,
-                mLeakDetector,
-                mStatusBarService,
-                mock(DumpManager.class),
-                mBgExecutor
-        );
-        mEntryManager.initialize(
-                mNotificationListener,
-                new NotificationRankingManager(
-                        () -> mNotificationMediaManager,
-                        mGroupManager,
-                        mHeadsUpManager,
-                        mock(NotificationFilter.class),
-                        mLogger,
-                        mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class),
-                        mock(HighPriorityProvider.class),
-                        mEnvironment));
-        mEntryManager.addNotificationEntryListener(mEntryListener);
-        mEntryManager.addCollectionListener(mNotifCollectionListener);
-        mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
-
-        setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
-    }
-
-    @Test
-    public void testAddNotification_noDuplicateEntriesCreated() {
-        // GIVEN a notification has been added
-        mEntryManager.addNotification(mSbn, mRankingMap);
-
-        // WHEN the same notification is added multiple times before the previous entry (with
-        // the same key) didn't finish inflating
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        mEntryManager.addNotification(mSbn, mRankingMap);
-
-        // THEN getAllNotifs() only contains exactly one notification with this key
-        int count = 0;
-        for (NotificationEntry entry : mEntryManager.getAllNotifs()) {
-            if (entry.getKey().equals(mSbn.getKey())) {
-                count++;
-            }
-        }
-        assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. "
-                        + "Instead there are " + count, 1, count);
-    }
-
-    @Test
-    public void testAddNotification_setsUserSentiment() {
-        mEntryManager.addNotification(mSbn, mRankingMap);
-
-        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
-                NotificationEntry.class);
-        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
-        NotificationEntry entry = entryCaptor.getValue();
-
-        assertEquals(entry.getUserSentiment(), Ranking.USER_SENTIMENT_NEUTRAL);
-    }
-
-    @Test
-    public void testUpdateNotification_prePostEntryOrder() throws Exception {
-        TestableLooper.get(this).processAllMessages();
-
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        mEntryManager.updateNotification(mSbn, mRankingMap);
-
-        // Ensure that update callbacks happen in correct order
-        InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
-        order.verify(mEntryListener).onPreEntryUpdated(mEntry);
-        order.verify(mEntryListener).onPostEntryUpdated(mEntry);
-    }
-
-    @Test
-    public void testRemoveNotification() {
-        mEntry.setRow(mRow);
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        verify(mEntryListener).onEntryRemoved(
-                argThat(matchEntryOnKey()), any(),
-                eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
-        verify(mRow).setRemoved();
-
-        assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-    }
-
-    @Test
-    public void testRemoveUninflatedNotification_removesNotificationFromAllNotifsList() {
-        // GIVEN an uninflated entry is added
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        assertTrue(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
-
-        // WHEN the uninflated entry is removed
-        mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
-                UNDEFINED_DISMISS_REASON);
-
-        // THEN the entry is still removed from the allNotifications list
-        assertFalse(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
-    }
-
-    @Test
-    public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
-        // GIVEN an entry manager with a notification
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        // GIVEN interceptor that doesn't intercept
-        when(mRemoveInterceptor.onNotificationRemoveRequested(
-                eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
-                .thenReturn(false);
-
-        // WHEN the notification entry is removed
-        mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
-
-        // THEN notification removal is sent to the server
-        FakeExecutor.exhaustExecutors(mBgExecutor);
-        verify(mStatusBarService).onNotificationClear(
-                mSbn.getPackageName(),
-                mSbn.getUser().getIdentifier(),
-                mSbn.getKey(),
-                mStats.dismissalSurface,
-                mStats.dismissalSentiment,
-                mStats.notificationVisibility);
-        verifyNoMoreInteractions(mStatusBarService);
-    }
-
-    @Test
-    public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
-
-        mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(),
-                eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
-    }
-
-    /** Regression test for b/201097913. */
-    @Test
-    public void testRemoveNotification_whilePending_onlyCollectionListenerNotified() {
-        // Add and then remove a pending entry (entry that hasn't been inflated).
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // Verify that only the listener for the NEW pipeline is notified.
-        // Old pipeline:
-        verify(mEntryListener, never()).onEntryRemoved(
-                argThat(matchEntryOnKey()), any(), anyBoolean(), anyInt());
-        // New pipeline:
-        verify(mNotifCollectionListener).onEntryRemoved(
-                argThat(matchEntryOnKey()), anyInt());
-    }
-
-    @Test
-    public void testUpdateNotificationRanking_noChange() {
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
-        mEntry.setRow(mRow);
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        setSmartActions(mEntry.getKey(), null);
-
-        mEntryManager.updateNotificationRanking(mRankingMap);
-        assertThat(mEntry.getSmartActions()).isEmpty();
-    }
-
-    @Test
-    public void testUpdateNotificationRanking_pendingNotification() {
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
-        mEntry.setRow(null);
-        mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
-        setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
-        mEntryManager.updateNotificationRanking(mRankingMap);
-        assertEquals(1, mEntry.getSmartActions().size());
-        assertEquals("action", mEntry.getSmartActions().get(0).title);
-    }
-
-    @Test
-    public void testUpdatePendingNotification_rankingUpdated() {
-        // GIVEN a notification with ranking is pending
-        final Ranking originalRanking = mEntry.getRanking();
-        mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
-
-        // WHEN the same notification has been updated with a new ranking
-        final int newRank = 2345;
-        doAnswer(invocationOnMock -> {
-            Ranking ranking = (Ranking)
-                    invocationOnMock.getArguments()[1];
-            ranking.populate(
-                    mEntry.getKey(),
-                    newRank, /* this changed!! */
-                    false,
-                    0,
-                    0,
-                    IMPORTANCE_DEFAULT,
-                    null, null,
-                    null, null, null, true,
-                    Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, null, null, false, false, false, null, 0, false);
-            return true;
-        }).when(mRankingMap).getRanking(eq(mEntry.getKey()), any(Ranking.class));
-        mEntryManager.addNotification(mSbn, mRankingMap);
-
-        // THEN ranking for the entry has been updated with new ranking
-        assertEquals(newRank, mEntry.getRanking().getRank());
-    }
-
-    @Test
-    public void testNotifyChannelModified_notifiesListeners() {
-        NotificationChannel channel = mock(NotificationChannel.class);
-        String pkg = "PKG";
-        mEntryManager.notifyChannelModified(pkg, UserHandle.CURRENT, channel,
-                NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
-        verify(mNotifCollectionListener).onNotificationChannelModified(eq(pkg),
-                eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
-        verify(mEntryListener).onNotificationChannelModified(eq(pkg),
-                eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
-    }
-
-    @Test
-    public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
-        // GIVEN an entry manager with a notification
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        // GIVEN a lifetime extender that always tries to extend lifetime
-        NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
-        when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
-        mEntryManager.addNotificationLifetimeExtender(extender);
-
-        // WHEN the notification is removed
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // THEN the extender is asked to manage the lifetime
-        verify(extender).setShouldManageLifetime(mEntry, true);
-        // THEN the notification is retained
-        assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener, never()).onEntryRemoved(
-                argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
-    }
-
-    @Test
-    public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() {
-        // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
-        mEntryManager.addNotificationLifetimeExtender(extender);
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-        assertTrue(extender.isManaging(mEntry.getKey()));
-
-        // WHEN the extender finishes its extension
-        extender.setExtendLifetimes(false);
-        extender.getCallback().onSafeToRemove(mEntry.getKey());
-
-        // THEN the notification is removed
-        assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener).onEntryRemoved(
-                argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
-    }
-
-    @Test
-    public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
-        // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
-        when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
-        mEntryManager.addNotificationLifetimeExtender(extender);
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // WHEN the notification is updated
-        mEntryManager.updateNotification(mEntry.getSbn(), mRankingMap);
-
-        // THEN the lifetime extension is canceled
-        verify(extender).setShouldManageLifetime(mEntry, false);
-    }
-
-    @Test
-    public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
-        // GIVEN an entry manager with a notification
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        // GIVEN two lifetime extenders, the first which never extends and the second which
-        // always extends
-        NotificationLifetimeExtender extender1 = mock(NotificationLifetimeExtender.class);
-        when(extender1.shouldExtendLifetime(mEntry)).thenReturn(false);
-        NotificationLifetimeExtender extender2 = mock(NotificationLifetimeExtender.class);
-        when(extender2.shouldExtendLifetime(mEntry)).thenReturn(true);
-        mEntryManager.addNotificationLifetimeExtender(extender1);
-        mEntryManager.addNotificationLifetimeExtender(extender2);
-
-        // GIVEN a notification was lifetime-extended and extender2 is managing it
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-        verify(extender1, never()).setShouldManageLifetime(mEntry, true);
-        verify(extender2).setShouldManageLifetime(mEntry, true);
-
-        // WHEN the extender1 changes its mind and wants to extend the lifetime of the notif
-        when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true);
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // THEN extender2 stops managing the notif and extender1 starts managing it
-        verify(extender1).setShouldManageLifetime(mEntry, true);
-        verify(extender2).setShouldManageLifetime(mEntry, false);
-    }
-
-    /**
-     * Ensure that calling NotificationEntryManager.performRemoveNotification() doesn't crash when
-     * given a notification that has already been removed from NotificationData.
-     */
-    @Test
-    public void testPerformRemoveNotification_removedEntry() {
-        mEntryManager.removeNotification(mSbn.getKey(), null, 0);
-        mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
-                REASON_CANCEL);
-    }
-
-    @Test
-    public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
-        // GIVEN an entry manager with a notification
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        // GIVEN interceptor that intercepts that entry
-        when(mRemoveInterceptor.onNotificationRemoveRequested(
-                eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
-                .thenReturn(true);
-
-        // WHEN the notification is removed
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // THEN the interceptor intercepts & the entry is not removed & no listeners are called
-        assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()),
-                any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
-    }
-
-    @Test
-    public void testRemoveInterceptor_notInterceptedGetsRemoved() {
-        // GIVEN an entry manager with a notification
-        mEntryManager.addActiveNotificationForTest(mEntry);
-
-        // GIVEN interceptor that doesn't intercept
-        when(mRemoveInterceptor.onNotificationRemoveRequested(
-                eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
-                .thenReturn(false);
-
-        // WHEN the notification is removed
-        mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
-        // THEN the interceptor intercepts & the entry is not removed & no listeners are called
-        assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-        verify(mEntryListener, atLeastOnce()).onEntryRemoved(argThat(matchEntryOnKey()),
-                any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
-    }
-
-    /* Tests annexed from NotificationDataTest go here */
-
-    @Test
-    public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
-        Notification.Builder n = new Notification.Builder(mContext, "di")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        NotificationEntry e2 = new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_NAME)
-                .setOpPkg(TEST_PACKAGE_NAME)
-                .setUid(TEST_UID)
-                .setId(mId++)
-                .setNotification(n.build())
-                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
-                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
-                .build();
-
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        mEntryManager.addActiveNotificationForTest(e2);
-
-        when(mEnvironment.isNotificationForCurrentProfiles(mEntry.getSbn())).thenReturn(false);
-        when(mEnvironment.isNotificationForCurrentProfiles(e2.getSbn())).thenReturn(true);
-
-        List<NotificationEntry> result = mEntryManager.getActiveNotificationsForCurrentUser();
-        assertEquals(result.size(), 1);
-        junit.framework.Assert.assertEquals(result.get(0), e2);
-    }
-
-    /* End annex */
-
-    private boolean entriesContainKey(Collection<NotificationEntry> entries, String key) {
-        for (NotificationEntry entry : entries) {
-            if (entry.getSbn().getKey().equals(key)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private Notification.Action createAction() {
-        return new Notification.Action.Builder(
-                Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
-                "action",
-                PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
-                    PendingIntent.FLAG_IMMUTABLE)).build();
-    }
-
-    private ArgumentMatcher<NotificationEntry> matchEntryOnKey() {
-        return e -> e.getKey().equals(mEntry.getKey());
-    }
-
-    private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender {
-        private NotificationSafeToRemoveCallback mCallback;
-        private boolean mExtendLifetimes = true;
-        private Set<String> mManagedNotifs = new ArraySet<>();
-
-        @Override
-        public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
-            return mExtendLifetimes;
-        }
-
-        @Override
-        public void setShouldManageLifetime(
-                @NonNull NotificationEntry entry,
-                boolean shouldManage) {
-            final boolean hasEntry = mManagedNotifs.contains(entry.getKey());
-            if (shouldManage) {
-                if (hasEntry) {
-                    throw new RuntimeException("Already managing this entry: " + entry.getKey());
-                }
-                mManagedNotifs.add(entry.getKey());
-            } else {
-                if (!hasEntry) {
-                    throw new RuntimeException("Not managing this entry: " + entry.getKey());
-                }
-                mManagedNotifs.remove(entry.getKey());
-            }
-        }
-
-        public void setExtendLifetimes(boolean extendLifetimes) {
-            mExtendLifetimes = extendLifetimes;
-        }
-
-        public NotificationSafeToRemoveCallback getCallback() {
-            return mCallback;
-        }
-
-        public boolean isManaging(String notificationKey) {
-            return mManagedNotifs.contains(notificationKey);
-        }
-    }
-
-    private NotificationEntry createNotification() {
-        Notification.Builder n = new Notification.Builder(mContext, "id")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        return new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_NAME)
-                .setOpPkg(TEST_PACKAGE_NAME)
-                .setUid(TEST_UID)
-                .setId(mId++)
-                .setNotification(n.build())
-                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
-                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
-                .build();
-    }
-
-    private static DismissedByUserStats defaultStats(NotificationEntry entry) {
-        return new DismissedByUserStats(
-                DISMISSAL_SHADE,
-                DISMISS_SENTIMENT_NEUTRAL,
-                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
deleted file mode 100644
index 2cacaf7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification;
-
-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.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.app.Notification;
-import android.app.Notification.MediaStyle;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.annotation.NonNull;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-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.provider.DebugModeFilterProvider;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationFilterTest extends SysuiTestCase {
-
-    private static final int UID_NORMAL = 123;
-    private static final int UID_ALLOW_DURING_SETUP = 456;
-    private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
-
-    private final StatusBarNotification mMockStatusBarNotification =
-            mock(StatusBarNotification.class);
-
-    @Mock
-    DebugModeFilterProvider mDebugModeFilterProvider;
-    @Mock
-    StatusBarStateController mStatusBarStateController;
-    @Mock
-    KeyguardEnvironment mEnvironment;
-    @Mock
-    ForegroundServiceController mFsc;
-    @Mock
-    NotificationLockscreenUserManager mUserManager;
-    @Mock
-    MediaFeatureFlag mMediaFeatureFlag;
-
-    private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
-
-    private NotificationFilter mNotificationFilter;
-    private ExpandableNotificationRow mRow;
-    private NotificationEntry mMediaEntry;
-    private MediaSession mMediaSession;
-
-    @Before
-    public void setUp() throws Exception {
-        allowTestableLooperAsMainThread();
-        MockitoAnnotations.initMocks(this);
-        when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
-
-        mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION");
-        NotificationEntryBuilder builder = new NotificationEntryBuilder();
-        builder.modifyNotification(mContext).setStyle(
-                new MediaStyle().setMediaSession(mMediaSession.getSessionToken()));
-        mMediaEntry = builder.build();
-
-        when(mMockPackageManager.checkUidPermission(
-                eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
-                eq(UID_NORMAL)))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockPackageManager.checkUidPermission(
-                eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
-                eq(UID_ALLOW_DURING_SETUP)))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-        mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
-        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
-                new NotificationGroupManagerLegacy(
-                        mock(StatusBarStateController.class),
-                        () -> mock(PeopleNotificationIdentifier.class),
-                        Optional.of(mock(Bubbles.class)),
-                        mock(DumpManager.class)));
-        mDependency.injectMockDependency(ShadeController.class);
-        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
-        mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
-        when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-        NotificationTestHelper testHelper = new NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this));
-        mRow = testHelper.createRow();
-        mNotificationFilter = newNotificationFilter();
-    }
-
-    @NonNull
-    private NotificationFilter newNotificationFilter() {
-        return new NotificationFilter(
-                mDebugModeFilterProvider,
-                mStatusBarStateController,
-                mEnvironment,
-                mFsc,
-                mUserManager,
-                mMediaFeatureFlag);
-    }
-
-    @After
-    public void tearDown() {
-        mMediaSession.release();
-    }
-
-    @Test
-    @UiThreadTest
-    public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() {
-        initStatusBarNotification(false);
-        when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
-
-        assertFalse(
-                NotificationFilter.showNotificationEvenIfUnprovisioned(
-                        mMockPackageManager,
-                        mMockStatusBarNotification));
-    }
-
-    @Test
-    @UiThreadTest
-    public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() {
-        initStatusBarNotification(true);
-
-        assertFalse(
-                NotificationFilter.showNotificationEvenIfUnprovisioned(
-                        mMockPackageManager,
-                        mMockStatusBarNotification));
-    }
-
-    @Test
-    @UiThreadTest
-    public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() {
-        initStatusBarNotification(true);
-        when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
-
-        assertTrue(
-                NotificationFilter.showNotificationEvenIfUnprovisioned(
-                        mMockPackageManager,
-                        mMockStatusBarNotification));
-    }
-
-    @Test
-    public void testShouldFilterHiddenNotifications() {
-        initStatusBarNotification(false);
-        // setup
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
-
-        // test should filter out hidden notifications:
-        // hidden
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSuspended(true)
-                .build();
-
-        assertTrue(mNotificationFilter.shouldFilterOut(entry));
-
-        // not hidden
-        entry = new NotificationEntryBuilder()
-                .setSuspended(false)
-                .build();
-        assertFalse(mNotificationFilter.shouldFilterOut(entry));
-    }
-
-    @Test
-    public void shouldFilterOtherNotificationWhenDisabled() {
-        // GIVEN that the media feature is disabled
-        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
-        NotificationFilter filter = newNotificationFilter();
-        // WHEN the media filter is asked about an entry
-        NotificationEntry otherEntry = new NotificationEntryBuilder().build();
-        final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
-        // THEN it shouldn't be filtered
-        assertFalse(shouldFilter);
-    }
-
-    @Test
-    public void shouldFilterOtherNotificationWhenEnabled() {
-        // GIVEN that the media feature is enabled
-        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
-        NotificationFilter filter = newNotificationFilter();
-        // WHEN the media filter is asked about an entry
-        NotificationEntry otherEntry = new NotificationEntryBuilder().build();
-        final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
-        // THEN it shouldn't be filtered
-        assertFalse(shouldFilter);
-    }
-
-    @Test
-    public void shouldFilterMediaNotificationWhenDisabled() {
-        // GIVEN that the media feature is disabled
-        when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
-        NotificationFilter filter = newNotificationFilter();
-        // WHEN the media filter is asked about a media entry
-        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
-        // THEN it shouldn't be filtered
-        assertFalse(shouldFilter);
-    }
-
-    @Test
-    public void shouldFilterMediaNotificationWhenEnabled() {
-        // GIVEN that the media feature is enabled
-        when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
-        NotificationFilter filter = newNotificationFilter();
-        // WHEN the media filter is asked about a media entry
-        final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
-        // THEN it should be filtered
-        assertTrue(shouldFilter);
-    }
-
-    private void initStatusBarNotification(boolean allowDuringSetup) {
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
-        Notification notification = new Notification.Builder(mContext, "test")
-                .addExtras(bundle)
-                .build();
-        when(mMockStatusBarNotification.getNotification()).thenReturn(notification);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
deleted file mode 100644
index 58abbf2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class VisualStabilityManagerTest extends SysuiTestCase {
-
-    private TestableLooper mTestableLooper;
-
-    private VisualStabilityManager mVisualStabilityManager;
-    private VisualStabilityProvider mVisualStabilityProvider = mock(VisualStabilityProvider.class);
-    private VisualStabilityManager.Callback mCallback = mock(VisualStabilityManager.Callback.class);
-    private VisibilityLocationProvider mLocationProvider = mock(VisibilityLocationProvider.class);
-    private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
-    private NotificationEntry mEntry;
-
-    private StatusBarStateController.StateListener mStatusBarStateListener;
-    private WakefulnessLifecycle.Observer mWakefulnessObserver;
-
-    @Before
-    public void setUp() {
-        StatusBarStateController statusBarStateController = mock(StatusBarStateController.class);
-        WakefulnessLifecycle wakefulnessLifecycle = mock(WakefulnessLifecycle.class);
-
-        mTestableLooper = TestableLooper.get(this);
-        mVisualStabilityManager = new VisualStabilityManager(
-                mock(NotificationEntryManager.class),
-                mVisualStabilityProvider,
-                new Handler(mTestableLooper.getLooper()),
-                statusBarStateController,
-                wakefulnessLifecycle,
-                mock(DumpManager.class));
-
-        mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
-        mEntry = new NotificationEntryBuilder().build();
-        mEntry.setRow(mRow);
-
-        when(mRow.getEntry()).thenReturn(mEntry);
-
-        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-        verify(statusBarStateController).addCallback(stateListenerCaptor.capture());
-        mStatusBarStateListener = stateListenerCaptor.getValue();
-
-        ArgumentCaptor<WakefulnessLifecycle.Observer> wakefulnessObserverCaptor =
-                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
-        verify(wakefulnessLifecycle).addObserver(wakefulnessObserverCaptor.capture());
-        mWakefulnessObserver = wakefulnessObserverCaptor.getValue();
-    }
-
-    @Test
-    public void testPanelExpansion() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        setPanelExpanded(false);
-        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testScreenOn() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        setScreenOn(false);
-        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testReorderingAllowedChangesScreenOn() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        setScreenOn(false);
-        assertTrue(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    @Test
-    public void testReorderingAllowedChangesPanel() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        setPanelExpanded(false);
-        assertTrue(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    @Test
-    public void testCallBackCalledScreenOn() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        setScreenOn(false);
-        verify(mCallback).onChangeAllowed();
-    }
-
-    @Test
-    public void testCallBackCalledPanelExpanded() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        setPanelExpanded(false);
-        verify(mCallback).onChangeAllowed();
-    }
-
-    @Test
-    public void testCallBackExactlyOnce() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        setScreenOn(false);
-        setScreenOn(true);
-        setScreenOn(false);
-        verify(mCallback).onChangeAllowed();
-    }
-
-    @Test
-    public void testCallBackCalledContinuouslyWhenRequested() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true  /* persistent */);
-        setScreenOn(false);
-        setScreenOn(true);
-        setScreenOn(false);
-        verify(mCallback, times(2)).onChangeAllowed();
-    }
-
-    @Test
-    public void testAddedCanReorder() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        mVisualStabilityManager.notifyViewAddition(mRow);
-        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testReorderingVisibleHeadsUpNotAllowed() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
-        mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
-        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testReorderingVisibleHeadsUpAllowed() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
-        mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
-        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testReorderingVisibleHeadsUpAllowedOnce() {
-        setPanelExpanded(true);
-        setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
-        mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
-        mVisualStabilityManager.onReorderingFinished();
-        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testPulsing() {
-        setPulsing(true);
-        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        setPulsing(false);
-        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
-    }
-
-    @Test
-    public void testReorderingAllowedChanges_Pulsing() {
-        setPulsing(true);
-        assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        setPulsing(false);
-        assertTrue(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    @Test
-    public void testCallBackCalled_Pulsing() {
-        setPulsing(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        setPulsing(false);
-        verify(mCallback).onChangeAllowed();
-    }
-
-    @Test
-    public void testTemporarilyAllowReorderingNotifiesCallbacks() {
-        // GIVEN having the panel open (which would block reordering)
-        setScreenOn(true);
-        setPanelExpanded(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-
-        // WHEN we temprarily allow reordering
-        mVisualStabilityManager.temporarilyAllowReordering();
-
-        // THEN callbacks are notified that reordering is allowed
-        verify(mCallback).onChangeAllowed();
-        assertTrue(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    @Test
-    public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
-        // GIVEN we are in a pulsing state
-        setPulsing(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-
-        // WHEN we temprarily allow reordering
-        mVisualStabilityManager.temporarilyAllowReordering();
-
-        // THEN reordering is still not allowed
-        verify(mCallback, never()).onChangeAllowed();
-        assertFalse(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    @Test
-    public void testTemporarilyAllowReorderingExpires() {
-        // GIVEN having the panel open (which would block reordering)
-        setScreenOn(true);
-        setPanelExpanded(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-
-        // WHEN we temprarily allow reordering and then wait until the window expires
-        mVisualStabilityManager.temporarilyAllowReordering();
-        assertTrue(mVisualStabilityManager.isReorderingAllowed());
-        mTestableLooper.processMessages(1);
-
-        // THEN reordering is no longer allowed
-        assertFalse(mVisualStabilityManager.isReorderingAllowed());
-    }
-
-    private void setPanelExpanded(boolean expanded) {
-        mStatusBarStateListener.onExpandedChanged(expanded);
-    }
-
-    private void setPulsing(boolean pulsing) {
-        mStatusBarStateListener.onPulsingChanged(pulsing);
-    }
-
-    private void setScreenOn(boolean screenOn) {
-        if (screenOn) {
-            mWakefulnessObserver.onStartedWakingUp();
-        } else {
-            mWakefulnessObserver.onFinishedGoingToSleep();
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
deleted file mode 100644
index c51c628..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ /dev/null
@@ -1,509 +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.collection
-
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
-import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_LOW
-import android.app.PendingIntent
-import android.app.Person
-import android.os.SystemClock
-import android.service.notification.NotificationListenerService.RankingMap
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
-import com.android.systemui.statusbar.notification.NotificationFilter
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import junit.framework.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class NotificationRankingManagerTest : SysuiTestCase() {
-
-    private val lazyMedia: Lazy<NotificationMediaManager> = Lazy {
-        mock(NotificationMediaManager::class.java)
-    }
-    private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier
-    private lateinit var rankingManager: TestableNotificationRankingManager
-    private lateinit var sectionsManager: NotificationSectionsFeatureManager
-    private lateinit var notificationFilter: NotificationFilter
-
-    @Before
-    fun setup() {
-        personNotificationIdentifier =
-                mock(PeopleNotificationIdentifier::class.java)
-        sectionsManager = mock(NotificationSectionsFeatureManager::class.java)
-        notificationFilter = mock(NotificationFilter::class.java)
-        rankingManager = TestableNotificationRankingManager(
-                lazyMedia,
-                mock(NotificationGroupManagerLegacy::class.java),
-                mock(HeadsUpManager::class.java),
-                notificationFilter,
-                mock(NotificationEntryManagerLogger::class.java),
-                sectionsManager,
-                personNotificationIdentifier,
-                HighPriorityProvider(
-                        personNotificationIdentifier,
-                        mock(NotificationGroupManagerLegacy::class.java)),
-                mock(KeyguardEnvironment::class.java)
-                )
-    }
-
-    @Test
-    fun testSort_highPriorityTrumpsNMSRank() {
-        // NMS rank says A and then B. But A is not high priority and B is, so B should sort in
-        // front
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_LOW) // low priority
-                .setRank(1) // NMS says rank first
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(
-                        Notification.Builder(mContext, "test")
-                                .build())
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH) // high priority
-                .setRank(2) // NMS says rank second
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(
-                        Notification.Builder(mContext, "test")
-                                .build())
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        assertEquals(
-                listOf(b, a),
-                rankingManager.updateRanking(null, listOf(a, b), "test"))
-    }
-
-    @Test
-    fun testSort_samePriorityUsesNMSRank() {
-        // NMS rank says A and then B, and they are the same priority so use that rank
-        val aN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val a = NotificationEntryBuilder()
-                .setRank(1)
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(aN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val bN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val b = NotificationEntryBuilder()
-                .setRank(2)
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(bN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        assertEquals(
-                listOf(a, b),
-                rankingManager.updateRanking(null, listOf(a, b), "test"))
-    }
-
-    @Test
-    fun testSort_headsUp_trumpsPeople() {
-        whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-        val aN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(aN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_IMPORTANT_PERSON)
-
-        val bN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(bN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-        b.row = mock(ExpandableNotificationRow::class.java).also {
-            whenever(it.isHeadsUp).thenReturn(true)
-        }
-
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_PERSON)
-
-        assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
-    }
-
-    @Test
-    fun testSort_importantPeople() {
-        whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-        val aN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(aN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_PERSON)
-
-        val bN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(bN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(b))
-                .thenReturn(TYPE_IMPORTANT_PERSON)
-
-        whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_IMPORTANT_PERSON))
-                .thenReturn(TYPE_IMPORTANT_PERSON.compareTo(TYPE_PERSON))
-        whenever(personNotificationIdentifier.compareTo(TYPE_IMPORTANT_PERSON, TYPE_PERSON))
-                .thenReturn(TYPE_PERSON.compareTo(TYPE_IMPORTANT_PERSON))
-
-        assertEquals(
-                listOf(b, a),
-                rankingManager.updateRanking(null, listOf(a, b), "test"))
-    }
-
-    @Test
-    fun testSort_fullPeople() {
-        whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-        val aN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(aN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_PERSON)
-
-        val bN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(bN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(b))
-                .thenReturn(TYPE_FULL_PERSON)
-
-        whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_FULL_PERSON))
-                .thenReturn(TYPE_FULL_PERSON.compareTo(TYPE_PERSON))
-        whenever(personNotificationIdentifier.compareTo(TYPE_FULL_PERSON, TYPE_PERSON))
-                .thenReturn(TYPE_PERSON.compareTo(TYPE_FULL_PERSON))
-
-        assertEquals(
-                listOf(b, a),
-                rankingManager.updateRanking(null, listOf(a, b), "test"))
-    }
-
-    @Test
-    fun testSort_properlySetsAlertingBucket() {
-        val notif = Notification.Builder(mContext, "test") .build()
-
-        val e = NotificationEntryBuilder()
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(notif)
-                .setUser(mContext.user)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setOverrideGroupKey("")
-                .build()
-
-        modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
-
-        rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
-        assertEquals(e.bucket, BUCKET_ALERTING)
-    }
-
-    @Test
-    fun testSort_properlySetsSilentBucket() {
-        val notif = Notification.Builder(mContext, "test") .build()
-
-        val e = NotificationEntryBuilder()
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(notif)
-                .setUser(mContext.user)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setOverrideGroupKey("")
-                .build()
-
-        modifyRanking(e).setImportance(IMPORTANCE_LOW).build()
-
-        rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
-        assertEquals(e.bucket, BUCKET_SILENT)
-    }
-
-    @Test
-    fun testFilter_resetsInitalizationTime() {
-        // GIVEN an entry that was initialized 1 second ago
-        val notif = Notification.Builder(mContext, "test") .build()
-
-        val e = NotificationEntryBuilder()
-            .setPkg("pkg")
-            .setOpPkg("pkg")
-            .setTag("tag")
-            .setNotification(notif)
-            .setUser(mContext.user)
-            .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-            .setOverrideGroupKey("")
-            .build()
-
-        e.setInitializationTime(SystemClock.elapsedRealtime() - 1000)
-        assertEquals(true, e.hasFinishedInitialization())
-
-        // WHEN we update ranking and filter out the notification entry
-        whenever(notificationFilter.shouldFilterOut(e)).thenReturn(true)
-        rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
-
-        // THEN the initialization time for the entry is reset
-        assertEquals(false, e.hasFinishedInitialization())
-    }
-
-    @Test
-    fun testSort_colorizedForegroundService() {
-        whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(
-                        Notification.Builder(mContext, "test")
-                                .build())
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_DEFAULT) // high priority
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(mock(Notification::class.java).also { notif ->
-                    whenever(notif.isForegroundService).thenReturn(true)
-                    whenever(notif.isColorized).thenReturn(true)
-                })
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val cN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val c = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(cN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_IMPORTANT_PERSON)
-
-        assertThat(rankingManager.updateRanking(null, listOf(a, b, c), "test"))
-                .containsExactly(b, c, a)
-        assertThat(b.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
-    }
-
-    @Test
-    fun testSort_importantCall() {
-        whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-
-        val a = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(
-                        Notification.Builder(mContext, "test")
-                                .build())
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val b = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_DEFAULT) // high priority
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(mock(Notification::class.java).also { notif ->
-                    whenever(notif.isForegroundService).thenReturn(true)
-                    whenever(notif.isColorized).thenReturn(true)
-                })
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.getUser())
-                .setOverrideGroupKey("")
-                .build()
-
-        val cN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
-        val c = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .setPkg("pkg")
-                .setOpPkg("pkg")
-                .setTag("tag")
-                .setNotification(cN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-
-        val dN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.CallStyle.forOngoingCall(
-                        Person.Builder().setName("caller").build(),
-                        mock(PendingIntent::class.java)))
-                .build()
-        val d = NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_DEFAULT) // high priority
-                .setPkg("pkg2")
-                .setOpPkg("pkg2")
-                .setTag("tag")
-                .setNotification(dN)
-                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
-                .setUser(mContext.user)
-                .setOverrideGroupKey("")
-                .build()
-        whenever(personNotificationIdentifier.getPeopleNotificationType(a))
-                .thenReturn(TYPE_IMPORTANT_PERSON)
-
-        assertThat(rankingManager.updateRanking(null, listOf(a, b, c, d), "test"))
-                .containsExactly(b, d, c, a)
-        assertThat(d.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
-    }
-
-    internal class TestableNotificationRankingManager(
-        mediaManager: Lazy<NotificationMediaManager>,
-        groupManager: NotificationGroupManagerLegacy,
-        headsUpManager: HeadsUpManager,
-        filter: NotificationFilter,
-        logger: NotificationEntryManagerLogger,
-        sectionsFeatureManager: NotificationSectionsFeatureManager,
-        peopleNotificationIdentifier: PeopleNotificationIdentifier,
-        highPriorityProvider: HighPriorityProvider,
-        keyguardEnvironment: KeyguardEnvironment
-    ) : NotificationRankingManager(
-        mediaManager,
-        groupManager,
-        headsUpManager,
-        filter,
-        logger,
-        sectionsFeatureManager,
-        peopleNotificationIdentifier,
-        highPriorityProvider,
-        keyguardEnvironment
-    ) {
-        fun applyTestRankingMap(r: RankingMap) {
-            rankingMap = r
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index e00e20f..46f630b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -33,9 +33,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -57,7 +55,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,8 +82,6 @@
     @Mock
     AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock
-    NotificationFilter mNotificationFilter;
-    @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
     KeyguardStateController mKeyguardStateController;
@@ -131,20 +126,10 @@
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#canAlertCommon(NotificationEntry)} will
-     * pass as long its provided NotificationEntry fulfills group suppression check.
-     */
-    private void ensureStateForAlertCommon() {
-        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
-    }
-
-    /**
-     * Sets up the state such that any requests to
      * {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills importance & DND checks.
      */
     private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
-        ensureStateForAlertCommon();
         when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
 
         when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -158,21 +143,10 @@
      * pass as long its provided NotificationEntry fulfills importance & DND checks.
      */
     private void ensureStateForHeadsUpWhenDozing() {
-        ensureStateForAlertCommon();
-
         when(mStatusBarStateController.isDozing()).thenReturn(true);
         when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
     }
 
-    /**
-     * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#shouldBubbleUp(NotificationEntry)} will
-     * pass as long its provided NotificationEntry fulfills importance & bubble checks.
-     */
-    private void ensureStateForBubbleUp() {
-        ensureStateForAlertCommon();
-    }
-
     @Test
     public void testDefaultSuppressorDoesNotSuppress() {
         // GIVEN a suppressor without any overrides
@@ -201,27 +175,6 @@
     }
 
     @Test
-    public void testShouldNotHeadsUpAwake_flteredOut() throws RemoteException {
-        // GIVEN state for "heads up when awake" is true
-        ensureStateForHeadsUpWhenAwake();
-
-        // WHEN this entry should be filtered out
-        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
-        when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
-
-        // THEN we shouldn't heads up this entry
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
-    }
-
-    @Test
-    public void testDoNotRunFilterOnNewPipeline() {
-        // WHEN this entry should be filtered out
-        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
-        mNotifInterruptionStateProvider.shouldHeadsUp(entry);
-        verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
-    }
-
-    @Test
     public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
         // GIVEN state for "heads up when awake" is true
         ensureStateForHeadsUpWhenAwake();
@@ -654,7 +607,6 @@
      */
     @Test
     public void testShouldBubbleUp() {
-        ensureStateForBubbleUp();
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
     }
 
@@ -664,7 +616,6 @@
      */
     @Test
     public void testShouldBubbleUp_notifInGroupWithOnlySummaryAlerts() {
-        ensureStateForBubbleUp();
         NotificationEntry bubble = createBubble("testgroup", GROUP_ALERT_SUMMARY);
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(bubble)).isTrue();
     }
@@ -674,8 +625,6 @@
      */
     @Test
     public void shouldNotBubbleUp_notAllowedToBubble() {
-        ensureStateForBubbleUp();
-
         NotificationEntry entry = createBubble();
         modifyRanking(entry)
                 .setCanBubble(false)
@@ -689,8 +638,6 @@
      */
     @Test
     public void shouldNotBubbleUp_notABubble() {
-        ensureStateForBubbleUp();
-
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         modifyRanking(entry)
                 .setCanBubble(true)
@@ -704,8 +651,6 @@
      */
     @Test
     public void shouldNotBubbleUp_invalidMetadata() {
-        ensureStateForBubbleUp();
-
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         modifyRanking(entry)
                 .setCanBubble(true)
@@ -717,8 +662,6 @@
 
     @Test
     public void shouldNotBubbleUp_suppressedInterruptions() {
-        ensureStateForBubbleUp();
-
         // If the notification can't heads up in general, it shouldn't bubble.
         mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions);
 
@@ -727,8 +670,6 @@
 
     @Test
     public void shouldNotBubbleUp_filteredOut() {
-        ensureStateForBubbleUp();
-
         // Make canAlertCommon false by saying it's filtered out
         when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
                 .thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index f57c409..b6a1bb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -26,7 +26,6 @@
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 
 import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
@@ -37,7 +36,6 @@
 import static org.mockito.ArgumentMatchers.anySet;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -77,7 +75,6 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -88,7 +85,6 @@
 import com.android.systemui.wmshell.BubblesManager;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -128,7 +124,6 @@
     @Mock private AccessibilityManager mAccessibilityManager;
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private INotificationManager mINotificationManager;
-    @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private LauncherApps mLauncherApps;
     @Mock private ShortcutManager mShortcutManager;
     @Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -160,7 +155,7 @@
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
                 Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
-                mShadeController, mock(DumpManager.class));
+                mShadeController);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -446,36 +441,6 @@
                 eq(mAssistantFeedbackController));
     }
 
-    @Test
-    public void testShouldExtendLifetime() {
-        NotificationGuts guts = new NotificationGuts(mContext);
-        ExpandableNotificationRow row = spy(createTestNotificationRow());
-        doReturn(guts).when(row).getGuts();
-        NotificationEntry entry = row.getEntry();
-        entry.setRow(row);
-        mGutsManager.setExposedGuts(guts);
-
-        assertTrue(mGutsManager.shouldExtendLifetime(entry));
-    }
-
-    @Test
-    @Ignore
-    public void testSetShouldManageLifetime_setShouldManage() {
-        NotificationEntry entry = createTestNotificationRow().getEntry();
-        mGutsManager.setShouldManageLifetime(entry, true /* shouldManage */);
-
-        assertTrue(entry.getKey().equals(mGutsManager.mKeyToRemoveOnGutsClosed));
-    }
-
-    @Test
-    public void testSetShouldManageLifetime_setShouldNotManage() {
-        NotificationEntry entry = createTestNotificationRow().getEntry();
-        mGutsManager.mKeyToRemoveOnGutsClosed = entry.getKey();
-        mGutsManager.setShouldManageLifetime(entry, false /* shouldManage */);
-
-        assertNull(mGutsManager.mKeyToRemoveOnGutsClosed);
-    }
-
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Utility methods:
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 820dea0..c25737d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -51,7 +51,6 @@
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -62,10 +61,11 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 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.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -84,7 +84,6 @@
 import com.android.systemui.tests.R;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.systemui.wmshell.BubblesTestActivity;
-import com.android.wm.shell.bubbles.Bubbles;
 
 import org.mockito.ArgumentCaptor;
 
@@ -112,8 +111,8 @@
     private final Context mContext;
     private final TestableLooper mTestLooper;
     private int mId;
-    private final NotificationGroupManagerLegacy mGroupMembershipManager;
-    private final NotificationGroupManagerLegacy mGroupExpansionManager;
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final GroupExpansionManager mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
     private final NotifBindPipeline mBindPipeline;
@@ -136,23 +135,18 @@
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(MediaOutputDialogFactory.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
-        mGroupMembershipManager = new NotificationGroupManagerLegacy(
-                mStatusBarStateController,
-                () -> mock(PeopleNotificationIdentifier.class),
-                Optional.of((mock(Bubbles.class))),
-                mock(DumpManager.class));
-        mGroupExpansionManager = mGroupMembershipManager;
+        mGroupMembershipManager = mock(GroupMembershipManager.class);
+        mGroupExpansionManager = mock(GroupExpansionManager.class);
         mHeadsUpManager = new HeadsUpManagerPhone(
                 mContext,
                 mock(HeadsUpManagerLogger.class),
                 mStatusBarStateController,
                 mock(KeyguardBypassController.class),
-                mock(NotificationGroupManagerLegacy.class),
+                mock(GroupMembershipManager.class),
                 mock(VisualStabilityProvider.class),
                 mock(ConfigurationControllerImpl.class),
                 new Handler(mTestLooper.getLooper())
         );
-        mGroupMembershipManager.setHeadsUpManager(mHeadsUpManager);
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
@@ -528,10 +522,6 @@
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
         inflateAndWait(entry);
 
-        // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
-        // the callback chain, so we need to make up for not adding it to the group manager
-        // here.
-        mGroupMembershipManager.onEntryAdded(entry);
         return row;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ac9fcc0..9d848e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,24 +18,7 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -50,28 +33,19 @@
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.ArrayList;
-import java.util.List;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -84,15 +58,11 @@
     @Mock private ConfigurationController mConfigurationController;
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
-    @Mock private NotificationRowComponent mNotificationRowComponent;
-    @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
-    @Mock private NotificationSectionsLogger mLogger;
     @Mock private MediaContainerController mMediaContainerController;
     @Mock private SectionHeaderController mIncomingHeaderController;
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -113,22 +83,11 @@
                     }
                     return count;
                 });
-        when(mNotificationRowComponent.getActivatableNotificationViewController())
-                .thenReturn(mActivatableNotificationViewController);
-        when(mMediaContainerController.getMediaContainerView())
-                .thenReturn(mock(MediaContainerView.class));
-        when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
-        when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
-        when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
-        when(mSilentHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
         mSectionsManager =
                 new NotificationSectionsManager(
-                        mStatusBarStateController,
                         mConfigurationController,
                         mKeyguardMediaController,
                         mSectionsFeatureManager,
-                        mLogger,
-                        mNotifPipelineFlags,
                         mMediaContainerController,
                         mIncomingHeaderController,
                         mPeopleHeaderController,
@@ -141,7 +100,6 @@
         mSectionsManager.initialize(mNssl);
         when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-
     }
 
     @Test(expected =  IllegalStateException.class)
@@ -149,641 +107,4 @@
         mSectionsManager.initialize(mNssl);
     }
 
-    @Test
-    public void testInsertHeader() {
-        // GIVEN a stack with HI and LO rows but no section headers
-        setStackState(
-                ALERTING,
-                ALERTING,
-                ALERTING,
-                GENTLE);
-
-        // WHEN we update the section headers
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN a LO section header is added
-        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
-    }
-
-    @Test
-    public void testRemoveHeader() {
-        // GIVEN a stack that originally had a header between the HI and LO sections
-        setStackState(
-                ALERTING,
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // WHEN the last LO row is replaced with a HI row
-        setStackState(
-                ALERTING,
-                ALERTING,
-                GENTLE_HEADER,
-                ALERTING);
-        clearInvocations(mNssl);
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN the LO section header is removed
-        verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
-    }
-
-    @Test
-    public void testDoNothingIfHeaderAlreadyRemoved() {
-        // GIVEN a stack with only HI rows
-        setStackState(
-                ALERTING,
-                ALERTING,
-                ALERTING);
-
-        // WHEN we update the sections headers
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN we don't add any section headers
-        verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
-    }
-
-    @Test
-    public void testMoveHeaderForward() {
-        // GIVEN a stack that originally had a header between the HI and LO sections
-        setStackState(
-                ALERTING,
-                ALERTING,
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // WHEN the LO section moves forward
-        setStackState(
-                ALERTING,
-                ALERTING,
-                GENTLE,
-                GENTLE_HEADER,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN the LO section header is also moved forward
-        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
-    }
-
-    @Test
-    public void testMoveHeaderBackward() {
-        // GIVEN a stack that originally had a header between the HI and LO sections
-        setStackState(
-                ALERTING,
-                GENTLE,
-                GENTLE,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // WHEN the LO section moves backward
-        setStackState(
-                ALERTING,
-                GENTLE_HEADER,
-                ALERTING,
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN the LO section header is also moved backward (with appropriate index shifting)
-        verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
-    }
-
-    @Test
-    public void testHeaderRemovedFromTransientParent() {
-        // GIVEN a stack where the header is animating away
-        setStackState(
-                ALERTING,
-                GENTLE_HEADER);
-        mSectionsManager.updateSectionBoundaries();
-        clearInvocations(mNssl);
-
-        SectionHeaderView silentHeaderView = mSectionsManager.getSilentHeaderView();
-        ViewGroup transientParent = mock(ViewGroup.class);
-        when(silentHeaderView.getTransientContainer()).thenReturn(transientParent);
-
-        // WHEN the LO section reappears
-        setStackState(
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // THEN the header is first removed from the transient parent before being added to the
-        // NSSL.
-        final InOrder inOrder = inOrder(silentHeaderView, mNssl);
-        inOrder.verify(silentHeaderView).removeFromTransientContainer();
-        inOrder.verify(mNssl).addView(eq(silentHeaderView), eq(1));
-    }
-
-    @Test
-    public void testHeaderNotShownOnLockscreen() {
-        // GIVEN a stack of HI and LO notifs on the lockscreen
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        setStackState(
-                ALERTING,
-                ALERTING,
-                ALERTING,
-                GENTLE);
-
-        // WHEN we update the section headers
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the section header is not added
-        verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
-    }
-
-    @Test
-    public void testHeaderShownWhenEnterLockscreen() {
-        // GIVEN a stack of HI and LO notifs on the lockscreen
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        setStackState(
-                ALERTING,
-                ALERTING,
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // WHEN we unlock
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the section header is added
-        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
-    }
-
-    @Test
-    public void testHeaderHiddenWhenEnterLockscreen() {
-        // GIVEN a stack of HI and LO notifs on the shade
-        setStackState(
-                ALERTING,
-                GENTLE_HEADER,
-                GENTLE);
-
-        // WHEN we go back to the keyguard
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the section header is removed
-        verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
-    }
-
-    @Test
-    public void testPeopleFiltering_onlyAddSilentHeader() {
-        enablePeopleFiltering();
-
-        setStackState(
-                PERSON,
-                ALERTING,
-                GENTLE);
-        mSectionsManager.updateSectionBoundaries();
-
-        verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
-    }
-
-    @Test
-    public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                PEOPLE_HEADER,
-                ALERTING,
-                PERSON,
-                ALERTING_HEADER,
-                GENTLE_HEADER,
-                GENTLE
-        );
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.HEADS_UP,
-                ChildType.PERSON,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE
-        );
-    }
-
-    @Test
-    public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                PERSON,
-                INCOMING_HEADER,
-                ALERTING,
-                PEOPLE_HEADER,
-                PERSON
-        );
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.HEADS_UP,
-                ChildType.HEADS_UP,
-                ChildType.PERSON
-        );
-    }
-
-    @Test
-    public void testPeopleFiltering_PersonHun() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                PERSON,
-                PEOPLE_HEADER,
-                PERSON
-        );
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.PERSON,
-                ChildType.PERSON
-        );
-    }
-
-    @Test
-    public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                ALERTING,
-                PERSON
-        );
-        mSectionsManager.updateSectionBoundaries();
-        verifyMockStack(
-                ChildType.HEADS_UP,
-                ChildType.PERSON
-        );
-    }
-
-    @Test
-    public void testPeopleFiltering_Fsn() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                INCOMING_HEADER,
-                ALERTING,
-                PEOPLE_HEADER,
-                FSN,
-                PERSON,
-                ALERTING,
-                GENTLE
-        );
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.HEADS_UP,
-                ChildType.FSN,
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE
-        );
-    }
-
-    @Test
-    public void testMediaControls_AddWhenEnterKeyguard() {
-        enableMediaControls();
-
-        // GIVEN a stack that doesn't include media controls
-        setStackState(ALERTING, GENTLE_HEADER, GENTLE);
-
-        // WHEN we go back to the keyguard
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        mSectionsManager.updateSectionBoundaries();
-
-        // Then the media controls are added
-        verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0);
-    }
-
-    @Test
-    public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() {
-        enableMediaControls();
-
-        // GIVEN a stack that doesn't include media
-        setupMockStack(
-                ALERTING,
-                ALERTING,
-                GENTLE_HEADER,
-                GENTLE);
-
-        // WHEN we go back to the keyguard
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.MEDIA_CONTROLS,
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE);
-    }
-
-    @Test
-    public void testRemoveNonSilentHeader() {
-        enablePeopleFiltering();
-        enableMediaControls();
-
-        setupMockStack(
-                MEDIA_CONTROLS,
-                INCOMING_HEADER,
-                PERSON,
-                ALERTING,
-                PEOPLE_HEADER,
-                ALERTING_HEADER,
-                ALERTING,
-                ALERTING,
-                GENTLE_HEADER,
-                GENTLE,
-                GENTLE
-        );
-
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.MEDIA_CONTROLS,
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE,
-                ChildType.GENTLE
-        );
-    }
-
-    @Test
-    public void testExpandIncomingSection() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                INCOMING_HEADER,
-                PERSON,
-                ALERTING,
-                PEOPLE_HEADER,
-                ALERTING,
-                PERSON,
-                ALERTING_HEADER,
-                ALERTING
-        );
-
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.HEADS_UP,
-                ChildType.HEADS_UP,
-                ChildType.HEADS_UP,
-                ChildType.PERSON,
-                ChildType.ALERTING
-        );
-    }
-
-    @Test
-    public void testIgnoreGoneView() {
-        enablePeopleFiltering();
-
-        setupMockStack(
-                PERSON.gone(),
-                ALERTING,
-                GENTLE
-        );
-
-        mSectionsManager.updateSectionBoundaries();
-
-        verifyMockStack(
-                ChildType.PERSON,
-                ChildType.ALERTING,
-                ChildType.GENTLE_HEADER,
-                ChildType.GENTLE
-        );
-    }
-
-    private void enablePeopleFiltering() {
-        when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
-    }
-
-    private void enableMediaControls() {
-        when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
-    }
-
-    private enum ChildType {
-        INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP,
-        FSN, PERSON, ALERTING, GENTLE, OTHER
-    }
-
-    private void setStackState(StackEntry... children) {
-        when(mNssl.getChildCount()).thenReturn(children.length);
-        for (int i = 0; i < children.length; i++) {
-            View child;
-            StackEntry entry = children[i];
-            switch (entry.mChildType) {
-                case INCOMING_HEADER:
-                    child = mSectionsManager.getIncomingHeaderView();
-                    break;
-                case MEDIA_CONTROLS:
-                    child = mSectionsManager.getMediaControlsView();
-                    break;
-                case PEOPLE_HEADER:
-                    child = mSectionsManager.getPeopleHeaderView();
-                    break;
-                case ALERTING_HEADER:
-                    child = mSectionsManager.getAlertingHeaderView();
-                    break;
-                case GENTLE_HEADER:
-                    child = mSectionsManager.getSilentHeaderView();
-                    break;
-                case FSN:
-                    child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
-                    break;
-                case PERSON:
-                    child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
-                    break;
-                case ALERTING:
-                    child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
-                    break;
-                case GENTLE:
-                    child = mockNotification(BUCKET_SILENT, entry.mIsGone);
-                    break;
-                case OTHER:
-                    child = mock(View.class);
-                    when(child.getVisibility()).thenReturn(View.VISIBLE);
-                    when(child.getParent()).thenReturn(mNssl);
-                    break;
-                default:
-                    throw new RuntimeException("Unknown ChildType: " + children[i]);
-            }
-            when(mNssl.getChildAt(i)).thenReturn(child);
-            when(mNssl.indexOfChild(child)).thenReturn(i);
-        }
-    }
-
-    private View mockNotification(@PriorityBucket int bucket, boolean isGone) {
-        ExpandableNotificationRow notifRow =
-                mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
-        when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-        when(notifRow.getParent()).thenReturn(mNssl);
-
-        NotificationEntry mockEntry = mock(NotificationEntry.class);
-        when(notifRow.getEntry()).thenReturn(mockEntry);
-
-        int[] bucketRef = new int[] { bucket };
-        when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
-        doAnswer(invocation -> {
-            bucketRef[0] = invocation.getArgument(0);
-            return null;
-        }).when(mockEntry).setBucket(anyInt());
-
-        when(notifRow.getVisibility()).thenReturn(isGone ? View.GONE : View.VISIBLE);
-        return notifRow;
-    }
-
-    private void verifyMockStack(ChildType... expected) {
-        final List<ChildType> actual = new ArrayList<>();
-        int childCount = mNssl.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mNssl.getChildAt(i);
-            if (child == mSectionsManager.getIncomingHeaderView()) {
-                actual.add(ChildType.INCOMING_HEADER);
-                continue;
-            }
-            if (child == mSectionsManager.getMediaControlsView()) {
-                actual.add(ChildType.MEDIA_CONTROLS);
-                continue;
-            }
-            if (child == mSectionsManager.getPeopleHeaderView()) {
-                actual.add(ChildType.PEOPLE_HEADER);
-                continue;
-            }
-            if (child == mSectionsManager.getAlertingHeaderView()) {
-                actual.add(ChildType.ALERTING_HEADER);
-                continue;
-            }
-            if (child == mSectionsManager.getSilentHeaderView()) {
-                actual.add(ChildType.GENTLE_HEADER);
-                continue;
-            }
-            if (child instanceof ExpandableNotificationRow) {
-                switch (((ExpandableNotificationRow) child).getEntry().getBucket()) {
-                    case BUCKET_HEADS_UP:
-                        actual.add(ChildType.HEADS_UP);
-                        break;
-                    case BUCKET_FOREGROUND_SERVICE:
-                        actual.add(ChildType.FSN);
-                        break;
-                    case BUCKET_PEOPLE:
-                        actual.add(ChildType.PERSON);
-                        break;
-                    case BUCKET_ALERTING:
-                        actual.add(ChildType.ALERTING);
-                        break;
-                    case BUCKET_SILENT:
-                        actual.add(ChildType.GENTLE);
-                        break;
-                    default:
-                        actual.add(ChildType.OTHER);
-                        break;
-                }
-                continue;
-            }
-            actual.add(ChildType.OTHER);
-        }
-        assertThat(actual).containsExactly((Object[]) expected).inOrder();
-    }
-
-    private void setupMockStack(StackEntry... entries) {
-        final List<View> children = new ArrayList<>();
-        when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
-        when(mNssl.getChildAt(anyInt()))
-                .thenAnswer(invocation -> {
-                    Integer index = invocation.getArgument(0);
-                    if (index == null || index < 0 || index >= children.size()) {
-                        return null;
-                    }
-                    return children.get(index);
-                });
-        when(mNssl.indexOfChild(any()))
-                .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
-        doAnswer(invocation -> {
-            View child = invocation.getArgument(0);
-            int index = invocation.getArgument(1);
-            children.add(index, child);
-            return null;
-        }).when(mNssl).addView(any(), anyInt());
-        doAnswer(invocation -> {
-            View child = invocation.getArgument(0);
-            children.remove(child);
-            return null;
-        }).when(mNssl).removeView(any());
-        doAnswer(invocation -> {
-            View child = invocation.getArgument(0);
-            int newIndex = invocation.getArgument(1);
-            children.remove(child);
-            children.add(newIndex, child);
-            return null;
-        }).when(mNssl).changeViewPosition(any(), anyInt());
-        for (StackEntry entry : entries) {
-            View child;
-            switch (entry.mChildType) {
-                case INCOMING_HEADER:
-                    child = mSectionsManager.getIncomingHeaderView();
-                    break;
-                case MEDIA_CONTROLS:
-                    child = mSectionsManager.getMediaControlsView();
-                    break;
-                case PEOPLE_HEADER:
-                    child = mSectionsManager.getPeopleHeaderView();
-                    break;
-                case ALERTING_HEADER:
-                    child = mSectionsManager.getAlertingHeaderView();
-                    break;
-                case GENTLE_HEADER:
-                    child = mSectionsManager.getSilentHeaderView();
-                    break;
-                case FSN:
-                    child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
-                    break;
-                case PERSON:
-                    child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
-                    break;
-                case ALERTING:
-                    child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
-                    break;
-                case GENTLE:
-                    child = mockNotification(BUCKET_SILENT, entry.mIsGone);
-                    break;
-                case OTHER:
-                    child = mock(View.class);
-                    when(child.getVisibility()).thenReturn(View.VISIBLE);
-                    when(child.getParent()).thenReturn(mNssl);
-                    break;
-                default:
-                    throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
-            }
-            children.add(child);
-        }
-    }
-
-    private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
-    private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
-    private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
-    private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
-    private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
-    private static final StackEntry FSN = new StackEntry(ChildType.FSN);
-    private static final StackEntry PERSON = new StackEntry(ChildType.PERSON);
-    private static final StackEntry ALERTING = new StackEntry(ChildType.ALERTING);
-    private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
-
-    private static class StackEntry {
-        final ChildType mChildType;
-        final boolean mIsGone;
-
-        StackEntry(ChildType childType) {
-            this(childType, false);
-        }
-
-        StackEntry(ChildType childType, boolean isGone) {
-            mChildType = childType;
-            mIsGone = isGone;
-        }
-
-        public StackEntry gone() {
-            return new StackEntry(mChildType, true);
-        }
-    }
 }
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 9bcea10..1460e04 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
@@ -67,6 +67,7 @@
 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;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -121,7 +122,8 @@
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private ScrimController mScrimController;
-    @Mock private NotificationGroupManagerLegacy mLegacyGroupManager;
+    @Mock private NotificationGroupManagerLegacy mGroupManagerLegacy;
+    @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private SectionHeaderController mSilentHeaderController;
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
     @Mock private NotifPipeline mNotifPipeline;
@@ -174,8 +176,8 @@
                 mNotificationSwipeHelperBuilder,
                 mCentralSurfaces,
                 mScrimController,
-                mLegacyGroupManager,
-                mLegacyGroupManager,
+                mGroupManagerLegacy,
+                mGroupExpansionManager,
                 mSilentHeaderController,
                 mNotifPipeline,
                 mNotifCollection,
@@ -184,7 +186,6 @@
                 mShadeTransitionController,
                 mUiEventLogger,
                 mRemoteInputManager,
-                mVisualStabilityManager,
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 37a48937..3c22edc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -101,8 +100,8 @@
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private SysuiStatusBarStateController mBarState;
-    @Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
-    @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
+    @Mock private GroupMembershipManager mGroupMembershipManger;
+    @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private DumpManager mDumpManager;
     @Mock private ExpandHelper mExpandHelper;
     @Mock private EmptyShadeView mEmptyShadeView;
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 7b7f45a..a0f7087 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
@@ -418,7 +418,6 @@
                 wakefulnessLifecycle,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
-                mVisualStabilityManager,
                 mDeviceProvisionedController,
                 mNavigationBarController,
                 mAccessibilityFloatingMenuController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index b8c8b5f..ac3d0c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -26,7 +26,6 @@
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -36,8 +35,8 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -61,19 +60,18 @@
     private HeadsUpManagerPhone mHeadsUpManager;
 
     @Mock private HeadsUpManagerLogger mHeadsUpManagerLogger;
-    @Mock private NotificationGroupManagerLegacy mGroupManager;
-    @Mock private View mNotificationShadeWindowView;
+    @Mock private GroupMembershipManager mGroupManager;
     @Mock private VisualStabilityProvider mVSProvider;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardBypassController mBypassController;
     @Mock private ConfigurationControllerImpl mConfigurationController;
     private boolean mLivesPastNormalTime;
 
-    private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
+    private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
                 Context context,
                 HeadsUpManagerLogger headsUpManagerLogger,
-                NotificationGroupManagerLegacy groupManager,
+                GroupMembershipManager groupManager,
                 VisualStabilityProvider visualStabilityProvider,
                 StatusBarStateController statusBarStateController,
                 KeyguardBypassController keyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
index 44325dd..a2828d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -16,19 +16,28 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.app.WallpaperManager
+import android.app.WallpaperManager.OnColorsChangedListener
 import android.graphics.Color
+import android.os.Handler
+import android.os.Looper
 import android.testing.AndroidTestingRunner
 import android.view.IWindowManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -38,17 +47,41 @@
 
     private val fakeSystemClock = FakeSystemClock()
     private val fakeExecutor = FakeExecutor(fakeSystemClock)
+    private val mainHandler = Handler(Looper.getMainLooper())
+
+    @get:Rule var expect: Expect = Expect.create()
 
     @Mock private lateinit var windowManager: IWindowManager
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var wallpaperManager: WallpaperManager
 
     private lateinit var provider: LetterboxBackgroundProvider
 
+    private var wallpaperColorsListener: OnColorsChangedListener? = null
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+        setUpWallpaperManager()
+        provider =
+            LetterboxBackgroundProvider(
+                windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler)
+    }
+
+    private fun setUpWallpaperManager() {
+        doAnswer { invocation ->
+                wallpaperColorsListener = invocation.arguments[0] as OnColorsChangedListener
+                return@doAnswer Unit
+            }
+            .`when`(wallpaperManager)
+            .addOnColorsChangedListener(any(), eq(mainHandler))
+        doAnswer {
+                wallpaperColorsListener = null
+                return@doAnswer Unit
+            }
+            .`when`(wallpaperManager)
+            .removeOnColorsChangedListener(any(OnColorsChangedListener::class.java))
     }
 
     @Test
@@ -76,6 +109,31 @@
     }
 
     @Test
+    fun letterboxBackgroundColor_returnsValueFromWindowManagerOnlyOnce() {
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+        provider.start()
+        fakeExecutor.runAllReady()
+        expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+        fakeExecutor.runAllReady()
+        expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+    }
+
+    @Test
+    fun letterboxBackgroundColor_afterWallpaperChanges_returnsUpdatedColor() {
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+        provider.start()
+        fakeExecutor.runAllReady()
+
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+        wallpaperColorsListener!!.onColorsChanged(null, 0)
+        fakeExecutor.runAllReady()
+
+        assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.GREEN)
+    }
+
+    @Test
     fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
         assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
deleted file mode 100644
index 56dfb0c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.os.Handler;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.wm.shell.bubbles.Bubbles;
-
-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.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.HashMap;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-
-    private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
-    private NotificationGroupManagerLegacy mGroupManager;
-    private HeadsUpManager mHeadsUpManager;
-    @Mock private NotificationEntryManager mNotificationEntryManager;
-    @Mock private RowContentBindStage mBindStage;
-    @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock StatusBarStateController mStatusBarStateController;
-    @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
-    private NotificationEntryListener mNotificationEntryListener;
-    private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
-    private final NotificationGroupTestHelper mGroupTestHelper =
-            new NotificationGroupTestHelper(mContext);
-
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mHeadsUpManager = new HeadsUpManager(mContext, mock(HeadsUpManagerLogger.class),
-                mock(Handler.class)) {};
-
-        when(mNotificationEntryManager.getPendingNotificationsIterator())
-                .thenReturn(mPendingEntries.values());
-
-        mGroupManager = new NotificationGroupManagerLegacy(
-                mStatusBarStateController,
-                () -> mPeopleNotificationIdentifier,
-                Optional.of(mock(Bubbles.class)),
-                mock(DumpManager.class));
-        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
-        mGroupManager.setHeadsUpManager(mHeadsUpManager);
-
-        when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
-
-        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(
-                mBindStage, mStatusBarStateController, mGroupManager);
-        mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
-
-        mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
-        verify(mNotificationEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
-        mNotificationEntryListener = mListenerCaptor.getValue();
-        mHeadsUpManager.addListener(mGroupAlertTransferHelper);
-    }
-
-    private void mockHasHeadsUpContentView(NotificationEntry entry,
-            boolean hasHeadsUpContentView) {
-        RowContentBindParams params = new RowContentBindParams();
-        if (hasHeadsUpContentView) {
-            params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
-        }
-        when(mBindStage.getStageParams(eq(entry))).thenReturn(params);
-    }
-
-    private void mockHasHeadsUpContentView(NotificationEntry entry) {
-        mockHasHeadsUpContentView(entry, true);
-    }
-
-    private void mockIsPriority(NotificationEntry priorityEntry) {
-        when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
-                .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpTransfersToChild() {
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mHeadsUpManager.showNotification(summaryEntry);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-
-        mockHasHeadsUpContentView(childEntry);
-
-        // Summary will be suppressed because there is only one child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // A suppressed summary should transfer its alert state to the child.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry2 =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Add second child notification so that summary is no longer suppressed.
-        mPendingEntries.put(childEntry2.getKey(), childEntry2);
-        mNotificationEntryListener.onPendingEntryAdded(childEntry2);
-        mGroupManager.onEntryAdded(childEntry2);
-
-        // The alert state should transfer back to the summary as there is now more than one
-        // child and the summary should no longer be suppressed.
-        assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpDoesntTransferBackOnDozingChanged() {
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry2 =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Set dozing to true.
-        mGroupAlertTransferHelper.onDozingChanged(true);
-
-        // Add second child notification so that summary is no longer suppressed.
-        mPendingEntries.put(childEntry2.getKey(), childEntry2);
-        mNotificationEntryListener.onPendingEntryAdded(childEntry2);
-        mGroupManager.onEntryAdded(childEntry2);
-
-        // Dozing changed so no reason to re-alert summary.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpTransferDoesNotAlertChildIfUninflated() {
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mHeadsUpManager.showNotification(summaryEntry);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        mockHasHeadsUpContentView(childEntry, false);
-
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Alert is immediately removed from summary, but we do not show child yet either as its
-        // content is not inflated.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpTransferAlertsChildOnInflation() {
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mHeadsUpManager.showNotification(summaryEntry);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        mockHasHeadsUpContentView(childEntry, false);
-
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Child entry finishes its inflation.
-        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
-        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
-        callbackCaptor.getValue().onBindFinished(childEntry);
-
-        // Alert is immediately removed from summary, and we show child as its content is inflated.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
-    }
-
-    @Test
-    public void testSuppressedSummaryHeadsUpTransferBackAbortsChildInflation() {
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        RowContentBindParams params = new RowContentBindParams();
-        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
-
-        NotificationEntry childEntry2 =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Add second child notification so that summary is no longer suppressed.
-        mPendingEntries.put(childEntry2.getKey(), childEntry2);
-        mNotificationEntryListener.onPendingEntryAdded(childEntry2);
-        mGroupManager.onEntryAdded(childEntry2);
-
-        // Child entry finishes its inflation.
-        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
-        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
-        callbackCaptor.getValue().onBindFinished(childEntry);
-
-        assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0);
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-    }
-
-    @Test
-    public void testCleanUpPendingAlertInfo() {
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        mockHasHeadsUpContentView(childEntry, false);
-
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        mNotificationEntryListener.onEntryRemoved(
-                childEntry, null, false, UNDEFINED_DISMISS_REASON);
-
-        assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
-    }
-
-    @Test
-    public void testUpdateGroupChangeDoesNotTransfer() {
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        mockHasHeadsUpContentView(childEntry, false);
-
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Notify that entry changed groups.
-        StatusBarNotification oldNotification = childEntry.getSbn();
-        StatusBarNotification newSbn = spy(childEntry.getSbn().clone());
-        doReturn("other_group").when(newSbn).getGroupKey();
-        childEntry.setSbn(newSbn);
-        mGroupManager.onEntryUpdated(childEntry, oldNotification);
-
-        assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
-    }
-
-    @Test
-    public void testUpdateChildToSummaryDoesNotTransfer() {
-        final String tag = "fooTag";
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
-        NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47, tag);
-        mockHasHeadsUpContentView(childEntry, false);
-
-        mHeadsUpManager.showNotification(summaryEntry);
-        // Trigger a transfer of alert state from summary to child.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Update that child to a summary.
-        StatusBarNotification oldNotification = childEntry.getSbn();
-        childEntry.setSbn(
-                mGroupTestHelper.createSummaryNotification(
-                        Notification.GROUP_ALERT_SUMMARY, 47, tag).getSbn());
-        mGroupManager.onEntryUpdated(childEntry, oldNotification);
-
-        assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
-    }
-
-    @Test
-    public void testOverriddenSummaryHeadsUpTransfersToPriority() {
-        // Creation order is oldest to newest, meaning the priority will be deemed newest
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        mockIsPriority(priorityEntry);
-
-        // summary gets heads up
-        mHeadsUpManager.showNotification(summaryEntry);
-
-        mockHasHeadsUpContentView(summaryEntry);
-        mockHasHeadsUpContentView(priorityEntry);
-        mockHasHeadsUpContentView(childEntry);
-
-        // Summary will have an alertOverride.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(priorityEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // An overridden summary should transfer its alert state to the priority.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-    }
-
-    @Test
-    public void testOverriddenSummaryHeadsUpTransferDoesNotAlertPriorityIfUninflated() {
-        // Creation order is oldest to newest, meaning the priority will be deemed newest
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        mockIsPriority(priorityEntry);
-
-        // summary gets heads up
-        mHeadsUpManager.showNotification(summaryEntry);
-
-        mockHasHeadsUpContentView(summaryEntry);
-        mockHasHeadsUpContentView(priorityEntry, false);
-        mockHasHeadsUpContentView(childEntry);
-
-        // Summary will have an alertOverride.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(priorityEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // Alert is immediately removed from summary, but we do not show priority yet either as its
-        // content is not inflated.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-        assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(priorityEntry));
-    }
-
-    @Test
-    public void testOverriddenSummaryHeadsUpTransfersToPriorityButBackAgain() {
-        // Creation order is oldest to newest, meaning the child2 will ultimately be deemed newest
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(groupAlert);
-        mockIsPriority(priorityEntry);
-
-        // summary gets heads up
-        mHeadsUpManager.showNotification(summaryEntry);
-
-        mockHasHeadsUpContentView(summaryEntry);
-        mockHasHeadsUpContentView(priorityEntry);
-        mockHasHeadsUpContentView(childEntry);
-        mockHasHeadsUpContentView(childEntry2);
-
-        // Summary will have an alertOverride.
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(priorityEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        // An overridden summary should transfer its alert state to the priority.
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-
-        mGroupManager.onEntryAdded(childEntry2);
-
-        // An overridden summary should transfer its alert state to the priority.
-        assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry2.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-    }
-
-    @Test
-    public void testOverriddenSuppressedSummaryHeadsUpTransfersToChildThenToPriority() {
-        // Creation order is oldest to newest, meaning the priority will ultimately be deemed newest
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        mockIsPriority(priorityEntry);
-
-        // summary gets heads up
-        mHeadsUpManager.showNotification(summaryEntry);
-
-        mockHasHeadsUpContentView(summaryEntry);
-        mockHasHeadsUpContentView(priorityEntry);
-        mockHasHeadsUpContentView(childEntry);
-
-        // Summary will be suppressed, and the child will receive the alert
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
-
-        // Alert should be transferred "back" from the child to the priority
-        mGroupManager.onEntryAdded(priorityEntry);
-
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-    }
-
-    @Test
-    public void testOverriddenSuppressedSummaryHeadsUpTransfersToPriorityThenToChild() {
-        // Creation order is oldest to newest, meaning the child will ultimately be deemed newest
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
-        NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
-        mockIsPriority(priorityEntry);
-
-        // summary gets heads up
-        mHeadsUpManager.showNotification(summaryEntry);
-
-        mockHasHeadsUpContentView(summaryEntry);
-        mockHasHeadsUpContentView(priorityEntry);
-        mockHasHeadsUpContentView(childEntry);
-
-        // Summary will have alert override of the priority
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(priorityEntry);
-
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-
-        // Alert should be transferred "back" from the priority to the child (which is newer)
-        mGroupManager.onEntryAdded(childEntry);
-
-        assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
-        assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
-        assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
deleted file mode 100644
index d002cebe5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-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.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    private NotificationGroupManagerLegacy mGroupManager;
-    private final NotificationGroupTestHelper mGroupTestHelper =
-            new NotificationGroupTestHelper(mContext);
-
-    @Mock
-    PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock
-    HeadsUpManager mHeadsUpManager;
-
-    @Before
-    public void setup() {
-        mDependency.injectMockDependency(Bubbles.class);
-        initializeGroupManager();
-    }
-
-    private void initializeGroupManager() {
-        mGroupManager = new NotificationGroupManagerLegacy(
-                mock(StatusBarStateController.class),
-                () -> mPeopleNotificationIdentifier,
-                Optional.of(mock(Bubbles.class)),
-                mock(DumpManager.class));
-        mGroupManager.setHeadsUpManager(mHeadsUpManager);
-    }
-
-    @Test
-    public void testIsOnlyChildInGroup() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-
-        assertTrue(mGroupManager.isOnlyChildInGroup(childEntry));
-    }
-
-    @Test
-    public void testIsChildInGroupWithSummary() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-        mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
-        assertTrue(mGroupManager.isChildInGroup(childEntry));
-    }
-
-    @Test
-    public void testIsSummaryOfGroupWithChildren() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-        mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
-        assertTrue(mGroupManager.isGroupSummary(summaryEntry));
-        assertEquals(summaryEntry, mGroupManager.getGroupSummary(childEntry));
-    }
-
-    @Test
-    public void testRemoveChildFromGroupWithSummary() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-        mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
-        mGroupManager.onEntryRemoved(childEntry);
-
-        assertFalse(mGroupManager.isChildInGroup(childEntry));
-    }
-
-    @Test
-    public void testRemoveSummaryFromGroupWithSummary() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-        mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
-        mGroupManager.onEntryRemoved(summaryEntry);
-
-        assertNull(mGroupManager.getGroupSummary(childEntry));
-        assertFalse(mGroupManager.isGroupSummary(summaryEntry));
-    }
-
-    @Test
-    public void testHeadsUpEntryIsIsolated() {
-        NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-        mGroupManager.onEntryAdded(summaryEntry);
-        mGroupManager.onEntryAdded(childEntry);
-        mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-        when(mHeadsUpManager.isAlerting(childEntry.getKey())).thenReturn(true);
-
-        mGroupManager.onHeadsUpStateChanged(childEntry, true);
-
-        // Child entries that are heads upped should be considered separate groups visually even if
-        // they are the same group logically
-        assertEquals(childEntry, mGroupManager.getGroupSummary(childEntry));
-        assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(childEntry));
-    }
-
-    @Test
-    public void testAlertOverrideWithSiblings_0() {
-        helpTestAlertOverrideWithSiblings(0);
-    }
-
-    @Test
-    public void testAlertOverrideWithSiblings_1() {
-        helpTestAlertOverrideWithSiblings(1);
-    }
-
-    @Test
-    public void testAlertOverrideWithSiblings_2() {
-        helpTestAlertOverrideWithSiblings(2);
-    }
-
-    @Test
-    public void testAlertOverrideWithSiblings_3() {
-        helpTestAlertOverrideWithSiblings(3);
-    }
-
-    @Test
-    public void testAlertOverrideWithSiblings_9() {
-        helpTestAlertOverrideWithSiblings(9);
-    }
-
-    /**
-     * Helper for testing various sibling counts
-     */
-    private void helpTestAlertOverrideWithSiblings(int numSiblings) {
-        helpTestAlertOverride(
-                /* numSiblings */ numSiblings,
-                /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* expectAlertOverride */ true);
-    }
-
-    @Test
-    public void testAlertOverrideWithParentAlertAll() {
-        // tests that summary can have GROUP_ALERT_ALL and this still works
-        helpTestAlertOverride(
-                /* numSiblings */ 1,
-                /* summaryGroupAlert */ Notification.GROUP_ALERT_ALL,
-                /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* expectAlertOverride */ true);
-    }
-
-    @Test
-    public void testAlertOverrideWithParentAlertChild() {
-        // Tests that if the summary alerts CHILDREN, there's no alertOverride
-        helpTestAlertOverride(
-                /* numSiblings */ 1,
-                /* summaryGroupAlert */ Notification.GROUP_ALERT_CHILDREN,
-                /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* expectAlertOverride */ false);
-    }
-
-    @Test
-    public void testAlertOverrideWithChildrenAlertAll() {
-        // Tests that if the children alert ALL, there's no alertOverride
-        helpTestAlertOverride(
-                /* numSiblings */ 1,
-                /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
-                /* priorityGroupAlert */ Notification.GROUP_ALERT_ALL,
-                /* siblingGroupAlert */ Notification.GROUP_ALERT_ALL,
-                /* expectAlertOverride */ false);
-    }
-
-    /**
-     * This tests, for a group with a priority entry and the given number of siblings, that:
-     * 1) the priority entry is identified as the alertOverride for the group
-     * 2) the onAlertOverrideChanged method is called at that time
-     * 3) when the priority entry is removed, these are reversed
-     */
-    private void helpTestAlertOverride(int numSiblings,
-            @Notification.GroupAlertBehavior int summaryGroupAlert,
-            @Notification.GroupAlertBehavior int priorityGroupAlert,
-            @Notification.GroupAlertBehavior int siblingGroupAlert,
-            boolean expectAlertOverride) {
-        long when = 10000;
-        // Create entries in an order so that the priority entry can be deemed the newest child.
-        NotificationEntry[] siblings = new NotificationEntry[numSiblings];
-        for (int i = 0; i < numSiblings; i++) {
-            siblings[i] = mGroupTestHelper
-                    .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
-        }
-        NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
-
-        // The priority entry is an important conversation.
-        when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
-                .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
-
-        // Register a listener so we can verify that the event is sent.
-        OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
-        mGroupManager.registerGroupChangeListener(groupChangeListener);
-
-        // Add all the entries.  The order here shouldn't matter.
-        mGroupManager.onEntryAdded(summaryEntry);
-        for (int i = 0; i < numSiblings; i++) {
-            mGroupManager.onEntryAdded(siblings[i]);
-        }
-        mGroupManager.onEntryAdded(priorityEntry);
-
-        if (!expectAlertOverride) {
-            // Test expectation is that there will NOT be an alert, so verify that!
-            NotificationGroup summaryGroup =
-                    mGroupManager.getGroupForSummary(summaryEntry.getSbn());
-            assertNull(summaryGroup.alertOverride);
-            return;
-        }
-        int max2Siblings = Math.min(2, numSiblings);
-
-        // Verify that the summary group has the priority child as its alertOverride
-        NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
-        assertEquals(priorityEntry, summaryGroup.alertOverride);
-        verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
-        verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
-        if (numSiblings > 1) {
-            verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
-        }
-        verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
-        verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
-        verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
-        verifyNoMoreInteractions(groupChangeListener);
-
-        // Verify that only the priority notification is isolated from the group
-        assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
-        assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
-        // Verify that the siblings are NOT isolated from the group
-        for (int i = 0; i < numSiblings; i++) {
-            assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
-            assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
-        }
-
-        // Remove the priority notification to validate that it is removed as the alertOverride
-        mGroupManager.onEntryRemoved(priorityEntry);
-
-        // verify that the alertOverride is removed when the priority notification is
-        assertNull(summaryGroup.alertOverride);
-        verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
-        verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
-        verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
-        if (numSiblings == 0) {
-            verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
-        }
-        verifyNoMoreInteractions(groupChangeListener);
-    }
-
-    @Test
-    public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
-        long when = 10000;
-        int numSiblings = 2;
-        int groupAlert = Notification.GROUP_ALERT_SUMMARY;
-        // Create entries in an order so that the priority entry can be deemed the newest child.
-        NotificationEntry[] siblings = new NotificationEntry[numSiblings];
-        for (int i = 0; i < numSiblings; i++) {
-            siblings[i] =
-                    mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
-        }
-        NotificationEntry priorityEntry =
-                mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
-        NotificationEntry summaryEntry =
-                mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
-
-        // The priority entry is an important conversation.
-        when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
-                .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
-
-        // Register a listener so we can verify that the event is sent.
-        OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
-        mGroupManager.registerGroupChangeListener(groupChangeListener);
-
-        // Add all the entries.  The order here shouldn't matter.
-        mGroupManager.onEntryAdded(summaryEntry);
-        for (int i = 0; i < numSiblings; i++) {
-            mGroupManager.onEntryAdded(siblings[i]);
-        }
-        mGroupManager.onEntryAdded(priorityEntry);
-
-        int max2Siblings = Math.min(2, numSiblings);
-
-        // Verify that the summary group has the priority child as its alertOverride
-        NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
-        assertEquals(priorityEntry, summaryGroup.alertOverride);
-        verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
-        verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
-        if (numSiblings > 1) {
-            verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
-        }
-        verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
-        verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
-        verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
-        verifyNoMoreInteractions(groupChangeListener);
-
-        // Verify that only the priority notification is isolated from the group
-        assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
-        assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
-        // Verify that the siblings are NOT isolated from the group
-        for (int i = 0; i < numSiblings; i++) {
-            assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
-            assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
-        }
-
-        Log.d("NotificationGroupManagerLegacyTest",
-                "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update summary");
-
-        StatusBarNotification oldSummarySbn = mGroupTestHelper.incrementPost(summaryEntry, 10000);
-        mGroupManager.onEntryUpdated(summaryEntry, oldSummarySbn);
-
-        verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
-        verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
-        verifyNoMoreInteractions(groupChangeListener);
-
-        Log.d("NotificationGroupManagerLegacyTest",
-                "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update priority child");
-
-        StatusBarNotification oldPrioritySbn = mGroupTestHelper.incrementPost(priorityEntry, 10000);
-        mGroupManager.onEntryUpdated(priorityEntry, oldPrioritySbn);
-
-        verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
-        verify(groupChangeListener, times(2)).onGroupCreated(any(), eq(priorityEntry.getKey()));
-        verify(groupChangeListener, times(2))
-                .onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
-        verify(groupChangeListener, times(max2Siblings + 3)).onGroupsChanged();
-        verifyNoMoreInteractions(groupChangeListener);
-
-        Log.d("NotificationGroupManagerLegacyTest",
-                "testAlertOverrideWhenUpdatingSummaryAtEnd: Done");
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c896c0a..de43a1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -178,10 +178,10 @@
         bubbleSbn.getNotification().contentIntent = mContentIntent;
         bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
 
-        ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
-        activeNotifications.add(mNotificationRow.getEntry());
-        activeNotifications.add(mBubbleNotificationRow.getEntry());
-        when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
+//        ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
+//        activeNotifications.add(mNotificationRow.getEntry());
+//        activeNotifications.add(mBubbleNotificationRow.getEntry());
+//        when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()),
                 anyInt())).thenReturn(mFutureDismissalRunnable);
@@ -347,9 +347,6 @@
 
         // The content intent should NOT be sent on click.
         verifyZeroInteractions(mContentIntent);
-
-        // Notification should not be cancelled.
-        verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
     }
 
     @Test
@@ -380,9 +377,6 @@
         verify(mContentIntent).getIntent();
         verify(mContentIntent).isActivity();
         verifyNoMoreInteractions(mContentIntent);
-
-        // Notification should not be cancelled.
-        verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 23b1404..1ec4de9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -79,7 +79,7 @@
                 mNotificationLockscreenUserManager);
 
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
-                mock(NotificationGroupManagerLegacy.class), mNotificationLockscreenUserManager,
+                mock(GroupExpansionManager.class), mNotificationLockscreenUserManager,
                 mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
                 mActivityStarter, mShadeController, new CommandQueue(mContext),
                 mock(ActionClickLogger.class), mFakeExecutor));
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 0eaf359..9bf86f5 100644
--- a/packages/VpnDialogs/res/values-es/strings.xml
+++ b/packages/VpnDialogs/res/values-es/strings.xml
@@ -19,7 +19,7 @@
     <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string>
     <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando se active la conexión VPN."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita monitorizar el tráfico de red. Acéptalo solo si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparecerá en la pantalla cuando la VPN esté activa."</string>
-    <string name="legacy_title" msgid="192936250066580964">"VPN conectada"</string>
+    <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada"</string>
     <string name="session" msgid="6470628549473641030">"Sesión:"</string>
     <string name="duration" msgid="3584782459928719435">"Duración:"</string>
     <string name="data_transmitted" msgid="7988167672982199061">"Enviado:"</string>
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f903c20..a94bfe2 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -302,6 +302,10 @@
     // Package: android
     NOTE_WIFI_APM_NOTIFICATION = 73;
 
+    // Inform the user of bluetooth apm state changes.
+    // Package: android
+    NOTE_BT_APM_NOTIFICATION = 74;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dac23a7..3d8dc14 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -975,11 +975,6 @@
      */
     public abstract void setEnableRollbackCode(int token, int enableRollbackCode);
 
-    /**
-     * Ask the package manager to compile layouts in the given package.
-     */
-    public abstract boolean compileLayouts(String packageName);
-
     /*
      * Inform the package manager that the pending package install identified by
      * {@code token} can be completed.
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 1d457aa..02c6ca2 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -479,6 +479,10 @@
             if (length > max) {
                 // Log and fall through to create empty tombstone below
                 Slog.w(TAG, "Dropping: " + tag + " (" + length + " > " + max + " bytes)");
+                logDropboxDropped(
+                        FrameworkStatsLog.DROPBOX_ENTRY_DROPPED__DROP_REASON__ENTRY_TOO_LARGE,
+                        tag,
+                        0);
             } else {
                 temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
                 try (FileOutputStream out = new FileOutputStream(temp)) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 83e3b49..4a3f682a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3047,6 +3047,10 @@
 
         try {
             mVold.createUserKey(userId, serialNumber, ephemeral);
+            // New keys are always unlocked.
+            synchronized (mLock) {
+                mLocalUnlockedUsers.append(userId);
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3058,6 +3062,10 @@
 
         try {
             mVold.destroyUserKey(userId);
+            // Destroying a key also locks it.
+            synchronized (mLock) {
+                mLocalUnlockedUsers.remove(userId);
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e7ea903..6aa472f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -166,6 +166,7 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -5761,7 +5762,7 @@
     }
 
     void serviceTimeout(ProcessRecord proc) {
-        String anrMessage = null;
+        TimeoutRecord timeoutRecord = null;
         synchronized(mAm) {
             if (proc.isDebugging()) {
                 // The app's being debugged, ignore timeout.
@@ -5796,7 +5797,8 @@
                 mLastAnrDump = sw.toString();
                 mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                 mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
-                anrMessage = "executing service " + timeout.shortInstanceName;
+                String anrMessage = "executing service " + timeout.shortInstanceName;
+                timeoutRecord = TimeoutRecord.forServiceExec(anrMessage);
             } else {
                 Message msg = mAm.mHandler.obtainMessage(
                         ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -5806,13 +5808,15 @@
             }
         }
 
-        if (anrMessage != null) {
-            mAm.mAnrHelper.appNotResponding(proc, anrMessage);
+        if (timeoutRecord != null) {
+            mAm.mAnrHelper.appNotResponding(proc, timeoutRecord);
         }
     }
 
     void serviceForegroundTimeout(ServiceRecord r) {
         ProcessRecord app;
+        // Grab a timestamp before lock is taken.
+        long timeoutEndMs = SystemClock.uptimeMillis();
         synchronized (mAm) {
             if (!r.fgRequired || !r.fgWaiting || r.destroying) {
                 return;
@@ -5836,17 +5840,19 @@
                     + "Service.startForeground(): " + r;
             Message msg = mAm.mHandler.obtainMessage(
                     ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
+            TimeoutRecord timeoutRecord = TimeoutRecord.forServiceStartWithEndTime(annotation,
+                    timeoutEndMs);
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = app;
-            args.arg2 = annotation;
+            args.arg2 = timeoutRecord;
             msg.obj = args;
             mAm.mHandler.sendMessageDelayed(msg,
                     mAm.mConstants.mServiceStartForegroundAnrDelayMs);
         }
     }
 
-    void serviceForegroundTimeoutANR(ProcessRecord app, String annotation) {
-        mAm.mAnrHelper.appNotResponding(app, annotation);
+    void serviceForegroundTimeoutANR(ProcessRecord app, TimeoutRecord timeoutRecord) {
+        mAm.mAnrHelper.appNotResponding(app, timeoutRecord);
     }
 
     public void updateServiceApplicationInfoLocked(ApplicationInfo applicationInfo) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f1cf03..54a7811 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -357,6 +357,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
 import com.android.internal.policy.AttributeCache;
@@ -1700,7 +1701,7 @@
             case SERVICE_FOREGROUND_TIMEOUT_ANR_MSG: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mServices.serviceForegroundTimeoutANR((ProcessRecord) args.arg1,
-                        (String) args.arg2);
+                        (TimeoutRecord) args.arg2);
                 args.recycle();
             } break;
             case SERVICE_FOREGROUND_CRASH_MSG: {
@@ -5014,7 +5015,8 @@
                 hostingRecord.getType(),
                 hostingRecord.getName(),
                 shortAction,
-                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()));
+                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+                HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
         return true;
     }
 
@@ -6416,6 +6418,7 @@
 
     @Override
     public void appNotResponding(final String reason) {
+        TimeoutRecord timeoutRecord = TimeoutRecord.forApp("App requested: " + reason);
         final int callingPid = Binder.getCallingPid();
 
         synchronized (mPidsSelfLocked) {
@@ -6425,7 +6428,7 @@
             }
 
             mAnrHelper.appNotResponding(app, null, app.info, null, null, false,
-                    "App requested: " + reason);
+                    timeoutRecord);
         }
     }
 
@@ -14339,10 +14342,12 @@
                 if (oldRecord.resultTo != null) {
                     final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
                     try {
+                        oldRecord.mIsReceiverAppRunning = true;
                         oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
                                 oldRecord.intent,
                                 Activity.RESULT_CANCELED, null, null,
-                                false, false, oldRecord.userId, oldRecord.callingUid, callingUid);
+                                false, false, oldRecord.userId, oldRecord.callingUid, callingUid,
+                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failure ["
                                 + queue.mQueueName + "] sending broadcast result of "
@@ -17169,18 +17174,19 @@
         }
 
         @Override
-        public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
-            return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem, reason);
+        public long inputDispatchingTimedOut(int pid, boolean aboveSystem,
+                TimeoutRecord timeoutRecord) {
+            return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem,
+                    timeoutRecord);
         }
 
         @Override
         public boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
                 ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
-                boolean aboveSystem, String reason) {
+                boolean aboveSystem, TimeoutRecord timeoutRecord) {
             return ActivityManagerService.this.inputDispatchingTimedOut((ProcessRecord) proc,
                     activityShortComponentName, aInfo, parentShortComponentName,
-                    (WindowProcessController) parentProc, aboveSystem, reason);
-
+                    (WindowProcessController) parentProc, aboveSystem, timeoutRecord);
         }
 
         @Override
@@ -17501,7 +17507,8 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
+                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
@@ -17742,7 +17749,7 @@
         }
     }
 
-    long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
+    long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
         if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires permission " + FILTER_EVENTS);
         }
@@ -17753,7 +17760,7 @@
         final long timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
                 DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
-        if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
+        if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, timeoutRecord)) {
             return 0;
         }
 
@@ -17766,18 +17773,12 @@
      */
     boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
             ApplicationInfo aInfo, String parentShortComponentName,
-            WindowProcessController parentProcess, boolean aboveSystem, String reason) {
+            WindowProcessController parentProcess, boolean aboveSystem,
+            TimeoutRecord timeoutRecord) {
         if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires permission " + FILTER_EVENTS);
         }
 
-        final String annotation;
-        if (reason == null) {
-            annotation = "Input dispatching timed out";
-        } else {
-            annotation = "Input dispatching timed out (" + reason + ")";
-        }
-
         if (proc != null) {
             synchronized (this) {
                 if (proc.isDebugging()) {
@@ -17787,13 +17788,13 @@
                 if (proc.getActiveInstrumentation() != null) {
                     Bundle info = new Bundle();
                     info.putString("shortMsg", "keyDispatchingTimedOut");
-                    info.putString("longMsg", annotation);
+                    info.putString("longMsg", timeoutRecord.mReason);
                     finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
                     return true;
                 }
             }
             mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
-                    parentShortComponentName, parentProcess, aboveSystem, annotation);
+                    parentShortComponentName, parentProcess, aboveSystem, timeoutRecord);
         }
 
         return true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index dbabe99..4bbfa3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -51,6 +51,7 @@
 import android.app.IActivityTaskManager;
 import android.app.IStopUserCallback;
 import android.app.IUidObserver;
+import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
 import android.app.ProfilerInfo;
 import android.app.RemoteServiceException.CrashedByAdbException;
@@ -1951,31 +1952,36 @@
 
         // Register switch observer.
         final CountDownLatch switchLatch = new CountDownLatch(1);
-        mInterface.registerUserSwitchObserver(
-                new UserSwitchObserver() {
-                    @Override
-                    public void onUserSwitchComplete(int newUserId) {
-                        if (userId == newUserId) {
-                            switchLatch.countDown();
-                        }
-                    }
-                }, ActivityManagerShellCommand.class.getName());
-
-        // Switch.
-        boolean switched = mInterface.switchUser(userId);
-        if (!switched) {
-            // Switching failed, don't wait for the user switch observer.
-            return false;
-        }
-
-        // Wait.
+        final IUserSwitchObserver userSwitchObserver = new UserSwitchObserver() {
+            @Override
+            public void onUserSwitchComplete(int newUserId) {
+                if (userId == newUserId) {
+                    switchLatch.countDown();
+                }
+            }
+        };
         try {
-            switched = switchLatch.await(USER_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            getErrPrintWriter().println("Error: Thread interrupted unexpectedly.");
-        }
+            mInterface.registerUserSwitchObserver(userSwitchObserver,
+                    ActivityManagerShellCommand.class.getName());
 
-        return switched;
+            // Switch.
+            boolean switched = mInterface.switchUser(userId);
+            if (!switched) {
+                // Switching failed, don't wait for the user switch observer.
+                return false;
+            }
+
+            // Wait.
+            try {
+                switched = switchLatch.await(USER_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                getErrPrintWriter().println("Error: Thread interrupted unexpectedly.");
+            }
+
+            return switched;
+        } finally {
+            mInterface.unregisterUserSwitchObserver(userSwitchObserver);
+        }
     }
 
     int runSwitchUser(PrintWriter pw) throws RemoteException {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index e354495..f1d8353 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.TimeoutRecord;
 import com.android.server.wm.WindowProcessController;
 
 import java.util.ArrayList;
@@ -68,15 +69,16 @@
         mService = service;
     }
 
-    void appNotResponding(ProcessRecord anrProcess, String annotation) {
+    void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
         appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
                 null /* parentShortComponentName */, null /* parentProcess */,
-                false /* aboveSystem */, annotation);
+                false /* aboveSystem */, timeoutRecord);
     }
 
     void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
             ApplicationInfo aInfo, String parentShortComponentName,
-            WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
+            WindowProcessController parentProcess, boolean aboveSystem,
+            TimeoutRecord timeoutRecord) {
         final int incomingPid = anrProcess.mPid;
         synchronized (mAnrRecords) {
             if (incomingPid == 0) {
@@ -85,17 +87,19 @@
                 return;
             }
             if (mProcessingPid == incomingPid) {
-                Slog.i(TAG, "Skip duplicated ANR, pid=" + incomingPid + " " + annotation);
+                Slog.i(TAG,
+                        "Skip duplicated ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
                 return;
             }
             for (int i = mAnrRecords.size() - 1; i >= 0; i--) {
                 if (mAnrRecords.get(i).mPid == incomingPid) {
-                    Slog.i(TAG, "Skip queued ANR, pid=" + incomingPid + " " + annotation);
+                    Slog.i(TAG,
+                            "Skip queued ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
                     return;
                 }
             }
             mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
-                    parentShortComponentName, parentProcess, aboveSystem, annotation));
+                    parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
         }
         startAnrConsumerIfNeeded();
     }
@@ -175,7 +179,7 @@
         final int mPid;
         final String mActivityShortComponentName;
         final String mParentShortComponentName;
-        final String mAnnotation;
+        final TimeoutRecord mTimeoutRecord;
         final ApplicationInfo mAppInfo;
         final WindowProcessController mParentProcess;
         final boolean mAboveSystem;
@@ -183,12 +187,13 @@
 
         AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                 ApplicationInfo aInfo, String parentShortComponentName,
-                WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
+                WindowProcessController parentProcess, boolean aboveSystem,
+                TimeoutRecord timeoutRecord) {
             mApp = anrProcess;
             mPid = anrProcess.mPid;
             mActivityShortComponentName = activityShortComponentName;
             mParentShortComponentName = parentShortComponentName;
-            mAnnotation = annotation;
+            mTimeoutRecord = timeoutRecord;
             mAppInfo = aInfo;
             mParentProcess = parentProcess;
             mAboveSystem = aboveSystem;
@@ -196,7 +201,8 @@
 
         void appNotResponding(boolean onlyDumpSelf) {
             mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
-                    mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
+                    mParentShortComponentName, mParentProcess, mAboveSystem,
+                    mTimeoutRecord,
                     onlyDumpSelf);
         }
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6d520c3..9028eef 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -36,6 +36,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 
 import android.annotation.NonNull;
@@ -82,6 +84,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -321,7 +324,7 @@
     }
 
     private final void processCurBroadcastLocked(BroadcastRecord r,
-            ProcessRecord app, int receiverType, int processTemperature) throws RemoteException {
+            ProcessRecord app) throws RemoteException {
         if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                 "Process cur broadcast " + r + " for app " + app);
         final IApplicationThread thread = app.getThread();
@@ -347,7 +350,7 @@
         // Force an update, even if there are other pending requests, overall it still saves time,
         // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
         mService.enqueueOomAdjTargetLocked(app);
-        mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
 
         // Tell the application to launch this receiver.
         maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
@@ -367,10 +370,6 @@
             if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                     "Process cur broadcast " + r + " DELIVERED for app " + app);
             started = true;
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, app.uid,
-                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
-                    ActivityManagerService.getShortAction(r.intent.getAction()),
-                    receiverType, processTemperature);
         } finally {
             if (!started) {
                 if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
@@ -407,9 +406,8 @@
             }
             try {
                 mPendingBroadcast = null;
-                processCurBroadcastLocked(br, app,
-                        BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                        BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD);
+                br.mIsReceiverAppRunning = false;
+                processCurBroadcastLocked(br, app);
                 didSomething = true;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception in new application when starting receiver "
@@ -517,6 +515,22 @@
         final long finishTime = SystemClock.uptimeMillis();
         final long elapsed = finishTime - r.receiverTime;
         r.state = BroadcastRecord.IDLE;
+        final int curIndex = r.nextReceiver - 1;
+        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
+            final Object curReceiver = r.receivers.get(curIndex);
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
+                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
+                    ActivityManagerService.getShortAction(r.intent.getAction()),
+                    curReceiver instanceof BroadcastFilter
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
+                    r.mIsReceiverAppRunning
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
+                    r.dispatchTime - r.enqueueTime,
+                    r.receiverTime - r.dispatchTime,
+                    finishTime - r.receiverTime);
+        }
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
         }
@@ -640,7 +654,8 @@
     void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
             Intent intent, int resultCode, String data, Bundle extras,
             boolean ordered, boolean sticky, int sendingUser,
-            int receiverUid, int callingUid) throws RemoteException {
+            int receiverUid, int callingUid, long dispatchDelay,
+            long receiveDelay) throws RemoteException {
         // Send the intent to the receiver asynchronously using one-way binder calls.
         if (app != null) {
             final IApplicationThread thread = app.getThread();
@@ -674,12 +689,15 @@
             receiver.performReceive(intent, resultCode, data, extras, ordered,
                     sticky, sendingUser);
         }
-        FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
-                receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
-                callingUid == -1 ? Process.SYSTEM_UID : callingUid,
-                ActivityManagerService.getShortAction(intent.getAction()),
-                BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
-                BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
+        if (!ordered) {
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
+                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
+                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
+                    ActivityManagerService.getShortAction(intent.getAction()),
+                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
+                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
+                    dispatchDelay, receiveDelay, 0 /* finish_delay */);
+        }
     }
 
     private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
@@ -960,10 +978,11 @@
                 filter.receiverList.app.mReceivers.addCurReceiver(r);
                 mService.enqueueOomAdjTargetLocked(r.curApp);
                 mService.updateOomAdjPendingTargetsLocked(
-                        OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+                        OOM_ADJ_REASON_START_RECEIVER);
             }
         } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app);
+            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+                    OOM_ADJ_REASON_START_RECEIVER);
         }
 
         try {
@@ -983,7 +1002,9 @@
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                         new Intent(r.intent), r.resultCode, r.resultData,
                         r.resultExtras, r.ordered, r.initialSticky, r.userId,
-                        filter.receiverList.uid, r.callingUid);
+                        filter.receiverList.uid, r.callingUid,
+                        r.dispatchTime - r.enqueueTime,
+                        r.receiverTime - r.dispatchTime);
                 // parallel broadcasts are fire-and-forget, not bookended by a call to
                 // finishReceiverLocked(), so we manage their activity-start token here
                 if (filter.receiverList.app != null
@@ -1166,6 +1187,7 @@
             r.dispatchTime = SystemClock.uptimeMillis();
             r.dispatchRealTime = SystemClock.elapsedRealtime();
             r.dispatchClockTime = System.currentTimeMillis();
+            r.mIsReceiverAppRunning = true;
 
             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                 Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -1244,7 +1266,7 @@
                     // make sure all processes have correct oom and sched
                     // adjustments.
                     mService.updateOomAdjPendingTargetsLocked(
-                            OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+                            OOM_ADJ_REASON_START_RECEIVER);
                 }
 
                 // when we have no more ordered broadcast on this queue, stop logging
@@ -1326,17 +1348,25 @@
                     if (sendResult) {
                         if (r.callerApp != null) {
                             mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
-                                    r.callerApp);
+                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
                         }
                         try {
                             if (DEBUG_BROADCAST) {
                                 Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
                                         + r.intent.getAction() + " app=" + r.callerApp);
                             }
+                            if (r.dispatchTime == 0) {
+                                // The dispatch time here could be 0, in case it's a parallel
+                                // broadcast but it has a result receiver. Set it to now.
+                                r.dispatchTime = now;
+                            }
+                            r.mIsReceiverAppRunning = true;
                             performReceiveLocked(r.callerApp, r.resultTo,
                                     new Intent(r.intent), r.resultCode,
                                     r.resultData, r.resultExtras, false, false, r.userId,
-                                    r.callingUid, r.callingUid);
+                                    r.callingUid, r.callingUid,
+                                    r.dispatchTime - r.enqueueTime,
+                                    now - r.dispatchTime);
                             logBootCompletedBroadcastCompletionLatencyIfPossible(r);
                             // Set this to null so that the reference
                             // (local and remote) isn't kept in the mBroadcastHistory.
@@ -1493,6 +1523,7 @@
                     "Delivering ordered ["
                     + mQueueName + "] to registered "
                     + filter + ": " + r);
+            r.mIsReceiverAppRunning = true;
             deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
             if (r.receiver == null || !r.ordered) {
                 // The receiver has already finished, so schedule to
@@ -1856,9 +1887,8 @@
                 app.addPackage(info.activityInfo.packageName,
                         info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
                 maybeAddAllowBackgroundActivityStartsToken(app, r);
-                processCurBroadcastLocked(r, app,
-                        BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                        BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
+                r.mIsReceiverAppRunning = true;
+                processCurBroadcastLocked(r, app);
                 return;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception when sending broadcast to "
@@ -1892,7 +1922,8 @@
                 info.activityInfo.applicationInfo, true,
                 r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                 new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction()),
+                        r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
+                        : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
                 isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
                 (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
         if (r.curApp == null) {
@@ -2134,7 +2165,7 @@
         }
 
         ProcessRecord app = null;
-        String anrMessage = null;
+        TimeoutRecord timeoutRecord = null;
 
         Object curReceiver;
         if (r.nextReceiver > 0) {
@@ -2159,9 +2190,10 @@
         }
 
         if (app != null) {
-            anrMessage =
+            String anrMessage =
                     "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
                             + "ms";
+            timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
         }
 
         if (mPendingBroadcast == r) {
@@ -2173,8 +2205,8 @@
                 r.resultExtras, r.resultAbort, false);
         scheduleBroadcastsLocked();
 
-        if (!debugging && anrMessage != null) {
-            mService.mAnrHelper.appNotResponding(app, anrMessage);
+        if (!debugging && timeoutRecord != null) {
+            mService.mAnrHelper.appNotResponding(app, timeoutRecord);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ae91d75..ce4528b 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -135,6 +135,8 @@
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // info about the receiver that is currently running.
 
+    boolean mIsReceiverAppRunning; // Was the receiver's app already running.
+
     // Private refcount-management bookkeeping; start > 0
     static AtomicInteger sNextToken = new AtomicInteger(1);
 
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 45265ac..653b602 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
@@ -904,7 +905,7 @@
                     }
 
                     if (!enable && opt.isFrozen()) {
-                        unfreezeAppLSP(process);
+                        unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE);
 
                         // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
                         opt.setFreezerOverride(true);
@@ -1214,11 +1215,11 @@
 
     // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
     @GuardedBy("mAm")
-    void unfreezeTemporarily(ProcessRecord app) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         if (mUseFreezer) {
             synchronized (mProcLock) {
                 if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
-                    unfreezeAppLSP(app);
+                    unfreezeAppLSP(app, reason);
                     freezeAppAsyncLSP(app);
                 }
             }
@@ -1244,7 +1245,7 @@
     }
 
     @GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
-    void unfreezeAppInternalLSP(ProcessRecord app) {
+    void unfreezeAppInternalLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         final int pid = app.getPid();
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         if (opt.isPendingFreeze()) {
@@ -1325,14 +1326,14 @@
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                         pid,
                         (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
-                        app.processName));
+                        new Pair<String, Integer>(app.processName, reason)));
         }
     }
 
     @GuardedBy({"mAm", "mProcLock"})
-    void unfreezeAppLSP(ProcessRecord app) {
+    void unfreezeAppLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         synchronized (mFreezerLock) {
-            unfreezeAppInternalLSP(app);
+            unfreezeAppInternalLSP(app, reason);
         }
     }
 
@@ -1343,25 +1344,14 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid) {
+    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
                 return;
             }
             Slog.d(TAG_AM, "quick sync unfreeze " + pid);
-            try {
-                freezeBinder(pid, false);
-            } catch (RuntimeException e) {
-                Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
-                return;
-            }
-
-            try {
-                Process.setProcessFrozen(pid, app.uid, false);
-            } catch (Exception e) {
-                Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
-            }
+            unfreezeAppLSP(app, reason);
         }
     }
 
@@ -1392,25 +1382,17 @@
 
     void cancelAllCompactions(CancelCompactReason reason) {
         synchronized (mProcLock) {
-            int size = mPendingCompactionProcesses.size();
-            ProcessRecord record;
-            for (int i=0; i < size; ++i) {
-                record = mPendingCompactionProcesses.get(i);
-                cancelCompactionForProcess(record, reason);
-                // The process record is kept alive after compactions are cleared,
-                // so make sure to reset the compaction state to avoid skipping any future
-                // compactions due to a stale value here.
-                record.mOptRecord.setHasPendingCompact(false);
+            while(!mPendingCompactionProcesses.isEmpty()) {
+                cancelCompactionForProcess(mPendingCompactionProcesses.get(0), reason);
             }
             mPendingCompactionProcesses.clear();
         }
-        cancelCompaction();
     }
 
     @GuardedBy("mProcLock")
     void cancelCompactionForProcess(ProcessRecord app, CancelCompactReason cancelReason) {
         boolean cancelled = false;
-        if (!mPendingCompactionProcesses.isEmpty() && mPendingCompactionProcesses.contains(app)) {
+        if (mPendingCompactionProcesses.contains(app)) {
             app.mOptRecord.setHasPendingCompact(false);
             mPendingCompactionProcesses.remove(app);
             cancelled = true;
@@ -1888,9 +1870,11 @@
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
                     int frozenDuration = msg.arg2;
-                    String processName = (String) msg.obj;
+                    Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
+                    String processName = obj.first;
+                    int reason = obj.second;
 
-                    reportUnfreeze(pid, frozenDuration, processName);
+                    reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 default:
                     return;
@@ -1901,7 +1885,7 @@
         private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
             Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
                     + " " + proc.processName + " (" + reason + ")");
-            unfreezeAppLSP(proc);
+            unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
             freezeAppAsyncLSP(proc);
         }
 
@@ -1989,7 +1973,8 @@
                         FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                         pid,
                         name,
-                        unfrozenDuration);
+                        unfrozenDuration,
+                        FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE);
             }
 
             try {
@@ -2019,12 +2004,13 @@
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                 synchronized (mProcLock) {
-                    unfreezeAppLSP(proc);
+                    unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
                 }
             }
         }
 
-        private void reportUnfreeze(int pid, int frozenDuration, String processName) {
+        private void reportUnfreeze(int pid, int frozenDuration, String processName,
+                @OomAdjuster.OomAdjReason int reason) {
 
             EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
 
@@ -2035,7 +2021,39 @@
                         FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
                         pid,
                         processName,
-                        frozenDuration);
+                        frozenDuration,
+                        getUnfreezeReasonCode(reason));
+            }
+        }
+
+        private int getUnfreezeReasonCode(@OomAdjuster.OomAdjReason int oomAdjReason) {
+            switch (oomAdjReason) {
+                case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ACTIVITY;
+                case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__FINISH_RECEIVER;
+                case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_RECEIVER;
+                case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__BIND_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UNBIND_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__GET_PROVIDER;
+                case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__REMOVE_PROVIDER;
+                case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UI_VISIBILITY;
+                case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ALLOWLIST;
+                case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_BEGIN;
+                case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_END;
+                default:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE;
             }
         }
 
@@ -2049,7 +2067,7 @@
                 ProcessRecord app = mFrozenProcesses.get(pid);
                 if (app != null) {
                     Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
-                    unfreezeAppLSP(app);
+                    unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 4ff1a12..9abd01a 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -75,6 +75,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -935,7 +936,9 @@
                 return;
             }
 
-            mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
+            TimeoutRecord timeoutRecord = TimeoutRecord.forContentProvider(
+                    "ContentProvider not responding");
+            mService.mAnrHelper.appNotResponding(host, timeoutRecord);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index f88a8ce..efc2a27 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -16,10 +16,27 @@
 
 package com.android.server.am;
 
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
+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_TYPE_ALARM;
+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;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
-import android.os.ProcessStartTime;
 
 /**
  * This class describes various information required to start a process.
@@ -32,6 +49,9 @@
  *
  * The {@code mHostingZygote} field describes from which Zygote the new process should be spawned.
  *
+ * The {@code mTriggerType} field describes the trigger that started this processs. This could be
+ * an alarm or a push-message for a broadcast, for example. This is purely for logging and stats.
+ *
  * {@code mDefiningPackageName} contains the packageName of the package that defines the
  * component we want to start; this can be different from the packageName and uid in the
  * ApplicationInfo that we're creating the process with, in case the service is a
@@ -71,7 +91,10 @@
     public static final String HOSTING_TYPE_TOP_ACTIVITY = "top-activity";
     public static final String HOSTING_TYPE_EMPTY = "";
 
-    private @NonNull final String mHostingType;
+    public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
+    public static final String TRIGGER_TYPE_ALARM = "alarm";
+
+    @NonNull private final String mHostingType;
     private final String mHostingName;
     private final int mHostingZygote;
     private final String mDefiningPackageName;
@@ -79,11 +102,12 @@
     private final boolean mIsTopApp;
     private final String mDefiningProcessName;
     @Nullable private final String mAction;
+    @NonNull private final String mTriggerType;
 
     public HostingRecord(@NonNull String hostingType) {
         this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
                 -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
-                null /* action */);
+                null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName) {
@@ -91,22 +115,23 @@
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
-            @Nullable String action) {
+            @Nullable String action, @Nullable String triggerType) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
                 null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */,
-                null /* definingProcessName */, action);
+                null /* definingProcessName */, action, triggerType);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
             String definingPackageName, int definingUid, String definingProcessName) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
-                definingUid, false /* isTopApp */, definingProcessName, null /* action */);
+                definingUid, false /* isTopApp */, definingProcessName, null /* action */,
+                TRIGGER_TYPE_UNKNOWN);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
                 null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
-                null /* definingProcessName */, null /* action */);
+                null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     public HostingRecord(@NonNull String hostingType, String hostingName) {
@@ -121,12 +146,12 @@
     private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote) {
         this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
                 -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
-                null /* action */);
+                null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote,
             String definingPackageName, int definingUid, boolean isTopApp,
-            String definingProcessName, @Nullable String action) {
+            String definingProcessName, @Nullable String action, String triggerType) {
         mHostingType = hostingType;
         mHostingName = hostingName;
         mHostingZygote = hostingZygote;
@@ -135,6 +160,7 @@
         mIsTopApp = isTopApp;
         mDefiningProcessName = definingProcessName;
         mAction = action;
+        mTriggerType = triggerType;
     }
 
     public @NonNull String getType() {
@@ -188,6 +214,11 @@
         return mAction;
     }
 
+    /** Returns the type of trigger that led to this process start. */
+    public @NonNull String getTriggerType() {
+        return mTriggerType;
+    }
+
     /**
      * Creates a HostingRecord for a process that must spawn from the webview zygote
      * @param hostingName name of the component to be hosted in this process
@@ -197,7 +228,7 @@
             String definingPackageName, int definingUid, String definingProcessName) {
         return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
                 WEBVIEW_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
-                definingProcessName, null /* action */);
+                definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     /**
@@ -211,7 +242,7 @@
             int definingUid, String definingProcessName) {
         return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
                 APP_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
-                definingProcessName, null /* action */);
+                definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     /**
@@ -236,35 +267,49 @@
     public static int getHostingTypeIdStatsd(@NonNull String hostingType) {
         switch(hostingType) {
             case HOSTING_TYPE_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
             case HOSTING_TYPE_ADDED_APPLICATION:
-                return ProcessStartTime.HOSTING_TYPE_ADDED_APPLICATION;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
             case HOSTING_TYPE_BACKUP:
-                return ProcessStartTime.HOSTING_TYPE_BACKUP;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
             case HOSTING_TYPE_BROADCAST:
-                return ProcessStartTime.HOSTING_TYPE_BROADCAST;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
             case HOSTING_TYPE_CONTENT_PROVIDER:
-                return ProcessStartTime.HOSTING_TYPE_CONTENT_PROVIDER;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
             case HOSTING_TYPE_LINK_FAIL:
-                return ProcessStartTime.HOSTING_TYPE_LINK_FAIL;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
             case HOSTING_TYPE_ON_HOLD:
-                return ProcessStartTime.HOSTING_TYPE_ON_HOLD;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
             case HOSTING_TYPE_NEXT_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_NEXT_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
             case HOSTING_TYPE_NEXT_TOP_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
             case HOSTING_TYPE_RESTART:
-                return ProcessStartTime.HOSTING_TYPE_RESTART;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
             case HOSTING_TYPE_SERVICE:
-                return ProcessStartTime.HOSTING_TYPE_SERVICE;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
             case HOSTING_TYPE_SYSTEM:
-                return ProcessStartTime.HOSTING_TYPE_SYSTEM;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
             case HOSTING_TYPE_TOP_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_TOP_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
             case HOSTING_TYPE_EMPTY:
-                return ProcessStartTime.HOSTING_TYPE_EMPTY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
             default:
-                return ProcessStartTime.HOSTING_TYPE_UNKNOWN;
+                return PROCESS_START_TIME__TYPE__UNKNOWN;
+        }
+    }
+
+    /**
+     * Map the string triggerType to enum TriggerType defined in ProcessStartTime proto.
+     * @param triggerType
+     * @return enum TriggerType defined in ProcessStartTime proto
+     */
+    public static int getTriggerTypeForStatsd(@NonNull String triggerType) {
+        switch(triggerType) {
+            case TRIGGER_TYPE_ALARM:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+            default:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 9158891..4380b42 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -50,7 +50,8 @@
      * Logs the event when LMKD kills a process to reduce memory pressure.
      * Code: LMK_KILL_OCCURRED = 51
      */
-    public static void logKillOccurred(DataInputStream inputData) {
+    public static void logKillOccurred(DataInputStream inputData, int totalForegroundServices,
+            int procsWithForegroundServices) {
         try {
             final long pgFault = inputData.readLong();
             final long pgMajFault = inputData.readLong();
@@ -67,11 +68,10 @@
             final int thrashing = inputData.readInt();
             final int maxThrashing = inputData.readInt();
             final String procName = inputData.readUTF();
-
             FrameworkStatsLog.write(FrameworkStatsLog.LMK_KILL_OCCURRED, uid, procName, oomScore,
                     pgFault, pgMajFault, rssInBytes, cacheInBytes, swapInBytes, processStartTimeNS,
                     minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason), thrashing,
-                    maxThrashing);
+                    maxThrashing, totalForegroundServices, procsWithForegroundServices);
         } catch (IOException e) {
             Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED");
             return;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8759f230..12aa66b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -448,7 +448,7 @@
      */
     @GuardedBy({"mService", "mProcLock"})
     private boolean performUpdateOomAdjLSP(ProcessRecord app, int cachedAdj,
-            ProcessRecord topApp, long now) {
+            ProcessRecord topApp, long now, @OomAdjReason int oomAdjReason) {
         if (app.getThread() == null) {
             return false;
         }
@@ -492,7 +492,7 @@
             }
         }
 
-        return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime());
+        return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime(), oomAdjReason);
     }
 
     /**
@@ -592,7 +592,7 @@
         mPendingProcessSet.remove(app);
         app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
         boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp,
-                SystemClock.uptimeMillis());
+                SystemClock.uptimeMillis(), oomAdjReason);
         // The 'app' here itself might or might not be in the cycle, for example,
         // the case A <=> B vs. A -> B <=> C; anyway, if we spot a cycle here, re-compute them.
         if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
@@ -645,7 +645,7 @@
             processes.add(app);
             assignCachedAdjIfNecessary(processes);
             applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),
-                    SystemClock.elapsedRealtime());
+                    SystemClock.elapsedRealtime(), oomAdjReason);
         }
         mTmpProcessList.clear();
         mService.mOomAdjProfiler.oomAdjEnded();
@@ -941,7 +941,8 @@
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
-        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
+        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+                oomAdjReason);
         mNumServiceProcs = mNewNumServiceProcs;
 
         if (mService.mAlwaysFinishActivities) {
@@ -1119,7 +1120,7 @@
 
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
-            final long oldTime, final ActiveUids activeUids) {
+            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
         ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
         final int numLru = lruList.size();
 
@@ -1147,7 +1148,7 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
-                    applyOomAdjLSP(app, true, now, nowElapsed);
+                    applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
                 }
 
                 final ProcessServiceRecord psr = app.mServices;
@@ -2640,7 +2641,7 @@
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
     @GuardedBy({"mService", "mProcLock"})
     private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
-            long nowElapsed) {
+            long nowElapsed, @OomAdjReason int oomAdjReson) {
         boolean success = true;
         final ProcessStateRecord state = app.mState;
         final UidRecord uidRec = app.getUidRecord();
@@ -2799,7 +2800,7 @@
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
         }
 
-        updateAppFreezeStateLSP(app);
+        updateAppFreezeStateLSP(app, oomAdjReson);
 
         if (state.getReportedProcState() != state.getCurProcState()) {
             state.setReportedProcState(state.getCurProcState());
@@ -3160,7 +3161,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateAppFreezeStateLSP(ProcessRecord app) {
+    private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
@@ -3172,7 +3173,7 @@
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
         if (opt.isFrozen() && opt.shouldNotFreeze()) {
-            mCachedAppOptimizer.unfreezeAppLSP(app);
+            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
             return;
         }
 
@@ -3182,7 +3183,7 @@
                 && !opt.shouldNotFreeze()) {
             mCachedAppOptimizer.freezeAppAsyncLSP(app);
         } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
-            mCachedAppOptimizer.unfreezeAppLSP(app);
+            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index b27665a..3a8a077 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -50,6 +50,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
@@ -254,7 +255,9 @@
 
     void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
             String parentShortComponentName, WindowProcessController parentProcess,
-            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
+            boolean aboveSystem, TimeoutRecord timeoutRecord,
+            boolean onlyDumpSelf) {
+        String annotation = timeoutRecord.mReason;
         ArrayList<Integer> firstPids = new ArrayList<>(5);
         SparseArray<Boolean> lastPids = new SparseArray<>(20);
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42792bf6..ccbca76 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -814,7 +814,12 @@
                                                 < LmkdStatsReporter.KILL_OCCURRED_MSG_SIZE) {
                                             return false;
                                         }
-                                        LmkdStatsReporter.logKillOccurred(inputData);
+                                        Pair<Integer, Integer> temp = getNumForegroundServices();
+                                        final int totalForegroundServices = temp.first;
+                                        final int procsWithForegroundServices = temp.second;
+                                        LmkdStatsReporter.logKillOccurred(inputData,
+                                                totalForegroundServices,
+                                                procsWithForegroundServices);
                                         return true;
                                     case LMK_STATE_CHANGED:
                                         if (receivedLen
@@ -5123,6 +5128,26 @@
         }
     }
 
+    /**
+     * Get the number of foreground services in all processes and number of processes that have
+     * foreground service within.
+     */
+    Pair<Integer, Integer> getNumForegroundServices() {
+        int numForegroundServices = 0;
+        int procs = 0;
+        synchronized (mService) {
+            for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+                ProcessRecord pr = mLruProcesses.get(i);
+                int numFgs = pr.mServices.getNumForegroundServices();
+                if (numFgs > 0) {
+                    numForegroundServices += numFgs;
+                    procs++;
+                }
+            }
+        }
+        return new Pair<>(numForegroundServices, procs);
+    }
+
     private final class ImperceptibleKillRunner extends IUidObserver.Stub {
         private static final String EXTRA_PID = "pid";
         private static final String EXTRA_UID = "uid";
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 9951e98..67eb675 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -180,6 +180,16 @@
         mRepFgServiceTypes = foregroundServiceTypes;
     }
 
+    int getNumForegroundServices() {
+        int count = 0;
+        for (int i = 0, serviceCount = mServices.size(); i < serviceCount; i++) {
+            if (mServices.valueAt(i).isForeground) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     void updateHasTopStartedAlmostPerceptibleServices() {
         mHasTopStartedAlmostPerceptibleServices = false;
         mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = 0;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0817b0..470de8c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -99,6 +99,7 @@
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
         DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
+        DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 487d19a..6e289b1 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -220,16 +220,40 @@
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
 
+        boolean batteryModeSupported = false;
+        boolean perfModeSupported = false;
+        int [] modes = gameManagerService.getAvailableGameModes(packageName);
+
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+                perfModeSupported = true;
+            } else if (mode == GameManager.GAME_MODE_BATTERY) {
+                batteryModeSupported = true;
+            }
+        }
+
         switch (gameMode.toLowerCase(Locale.getDefault())) {
             case "2":
             case "performance":
-                gameManagerService.setGameModeConfigOverride(packageName, userId,
-                        GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio);
+                if (perfModeSupported) {
+                    gameManagerService.setGameModeConfigOverride(packageName, userId,
+                            GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio);
+                } else {
+                    pw.println("Game mode: " + gameMode + " not supported by "
+                            + packageName);
+                    return -1;
+                }
                 break;
             case "3":
             case "battery":
-                gameManagerService.setGameModeConfigOverride(packageName, userId,
-                        GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio);
+                if (batteryModeSupported) {
+                    gameManagerService.setGameModeConfigOverride(packageName, userId,
+                            GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio);
+                } else {
+                    pw.println("Game mode: " + gameMode + " not supported by "
+                            + packageName);
+                    return -1;
+                }
                 break;
             default:
                 pw.println("Invalid game mode: " + gameMode);
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index d16fe12..d4ef638 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -343,6 +343,9 @@
      *
      * Calling this multiple times for duplicate requests will be no-ops, returning true.
      *
+     * TODO(b/239130847): Maintain the proximity state in AttentionManagerService and change this
+     * to a polling API.
+     *
      * @return {@code true} if the framework was able to dispatch the request
      */
     @VisibleForTesting
@@ -853,9 +856,6 @@
     @GuardedBy("mLock")
     private void cancelAndUnbindLocked() {
         synchronized (mLock) {
-            if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
-                return;
-            }
             if (mCurrentAttentionCheck != null) {
                 cancel();
             }
@@ -937,7 +937,7 @@
             }
         }
 
-        class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal {
+        class TestableProximityUpdateCallbackInternal implements ProximityUpdateCallbackInternal {
             private double mLastCallbackCode = PROXIMITY_UNKNOWN;
 
             @Override
@@ -1069,6 +1069,7 @@
         private void resetStates() {
             synchronized (mLock) {
                 mCurrentProximityUpdate = null;
+                cancelAndUnbindLocked();
             }
             mComponentName = resolveAttentionService(mContext);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
new file mode 100644
index 0000000..0f1fe68
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Common attributes for all biometric service providers.
+ *
+ * @param <T> Internal settings type.
+ */
+public interface BiometricServiceProvider<T extends SensorPropertiesInternal> {
+
+    /** Checks if the specified sensor is owned by this provider. */
+    boolean containsSensor(int sensorId);
+
+    /** All sensor properties. */
+    @NonNull
+    List<T> getSensorProperties();
+
+    /** Properties for the given sensor id. */
+    @NonNull
+    T getSensorProperties(int sensorId);
+
+    boolean isHardwareDetected(int sensorId);
+
+    /** If the user has any enrollments for the given sensor. */
+    boolean hasEnrollments(int sensorId, int userId);
+
+    long getAuthenticatorId(int sensorId, int userId);
+
+    @LockoutTracker.LockoutMode
+    int getLockoutModeForUser(int sensorId, int userId);
+
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer);
+
+    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
new file mode 100644
index 0000000..7574523
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Handler;
+import android.os.IInterface;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Container for all BiometricServiceProvider implementations.
+ *
+ * @param <T> The service provider type.
+ * @param <P> The internal properties type.
+ * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}.
+ */
+public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>,
+        P extends SensorPropertiesInternal,
+        C extends IInterface> {
+
+    private static final String TAG = "BiometricServiceRegistry";
+
+    // Volatile so they can be read without a lock once all services are registered.
+    // But, ideally remove this and provide immutable copies via the callback instead.
+    @Nullable
+    private volatile List<T> mServiceProviders;
+    @Nullable
+    private volatile List<P> mAllProps;
+
+    @NonNull
+    private final Supplier<IBiometricService> mBiometricServiceSupplier;
+    @NonNull
+    private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>();
+
+    public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) {
+        mBiometricServiceSupplier = biometricSupplier;
+    }
+
+    /**
+     * Register an implementation by creating a new authenticator and initializing it via
+     * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)}
+     * using the given properties.
+     *
+     * @param service service to register with
+     * @param props   internal properties to initialize the authenticator
+     */
+    protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props);
+
+    /**
+     * Invoke the callback to notify clients that all authenticators have been registered.
+     *
+     * @param callback callback to invoke
+     * @param allProps properties of all authenticators
+     */
+    protected abstract void invokeRegisteredCallback(@NonNull C callback,
+            @NonNull List<P> allProps) throws RemoteException;
+
+    /**
+     * Register all authenticators in a background thread.
+     *
+     * @param serviceProvider Supplier function that will be invoked on the background thread.
+     */
+    public void registerAll(Supplier<List<T>> serviceProvider) {
+        // Some HAL might not be started before the system service and will cause the code below
+        // to wait, and some of the operations below might take a significant amount of time to
+        // complete (calls to the HALs). To avoid blocking the rest of system server we put
+        // this on a background thread.
+        final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+                true /* allowIo */);
+        thread.start();
+        final Handler handler = new Handler(thread.getLooper());
+        handler.post(() -> registerAllInBackground(serviceProvider));
+        thread.quitSafely();
+    }
+
+    /** Register authenticators now, only called by {@link #registerAll(Supplier).} */
+    @VisibleForTesting
+    public void registerAllInBackground(Supplier<List<T>> serviceProvider) {
+        List<T> providers = serviceProvider.get();
+        if (providers == null) {
+            providers = new ArrayList<>();
+        }
+
+        final IBiometricService biometricService = mBiometricServiceSupplier.get();
+        if (biometricService == null) {
+            throw new IllegalStateException("biometric service cannot be null");
+        }
+
+        // Register each sensor individually with BiometricService
+        final List<P> allProps = new ArrayList<>();
+        for (T provider : providers) {
+            final List<P> props = provider.getSensorProperties();
+            for (P prop : props) {
+                registerService(biometricService, prop);
+            }
+            allProps.addAll(props);
+        }
+
+        finishRegistration(providers, allProps);
+    }
+
+    private synchronized void finishRegistration(
+            @NonNull List<T> providers, @NonNull List<P> allProps) {
+        mServiceProviders = Collections.unmodifiableList(providers);
+        mAllProps = Collections.unmodifiableList(allProps);
+        broadcastAllAuthenticatorsRegistered();
+    }
+
+    /**
+     * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)}
+     * has finished registering all providers (executes immediately if already done).
+     *
+     * @param callback registration callback
+     */
+    public synchronized void addAllRegisteredCallback(@Nullable C callback) {
+        if (callback == null) {
+            Slog.e(TAG, "addAllRegisteredCallback, callback is null");
+            return;
+        }
+
+        final boolean registered = mRegisteredCallbacks.register(callback);
+        final boolean allRegistered = mServiceProviders != null;
+        if (registered && allRegistered) {
+            broadcastAllAuthenticatorsRegistered();
+        } else if (!registered) {
+            Slog.e(TAG, "addAllRegisteredCallback failed to register callback");
+        }
+    }
+
+    private synchronized void broadcastAllAuthenticatorsRegistered() {
+        final int n = mRegisteredCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            final C cb = mRegisteredCallbacks.getBroadcastItem(i);
+            try {
+                invokeRegisteredCallback(cb, mAllProps);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e);
+            } finally {
+                mRegisteredCallbacks.unregister(cb);
+            }
+        }
+        mRegisteredCallbacks.finishBroadcast();
+    }
+
+    /**
+     * Get a list of registered providers.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @NonNull
+    public List<T> getProviders() {
+        return mServiceProviders != null ? mServiceProviders : Collections.emptyList();
+    }
+
+    /**
+     * Gets the provider for given sensor id or null if not registered.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @Nullable
+    public T getProviderForSensor(int sensorId) {
+        if (mServiceProviders != null) {
+            for (T provider : mServiceProviders) {
+                if (provider.containsSensor(sensorId)) {
+                    return provider;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * For devices with only a single provider, returns that provider.
+     * If no providers, or multiple providers exist, returns null.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @Nullable
+    public Pair<Integer, T> getSingleProvider() {
+        if (mAllProps == null || mAllProps.size() != 1) {
+            Slog.e(TAG, "Multiple sensors found: " + mAllProps.size());
+            return null;
+        }
+
+        final int sensorId = mAllProps.get(0).sensorId;
+        final T provider = getProviderForSensor(sensorId);
+        if (provider != null) {
+            return new Pair<>(sensorId, provider);
+        }
+
+        Slog.e(TAG, "Single sensor, but provider not found");
+        return null;
+    }
+
+    /**
+     * Get the properties for all providers.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @NonNull
+    public List<P> getAllProperties() {
+        return mAllProps != null ? mAllProps : Collections.emptyList();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 0d789f7..f854316 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -23,32 +23,64 @@
 import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.IBiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * A callback for receiving notifications about biometric sensor state changes.
+ *
+ * @param <T> service provider type
+ * @param <P> internal property type
  */
-public class BiometricStateCallback implements ClientMonitorCallback {
+public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
+        P extends SensorPropertiesInternal> implements ClientMonitorCallback {
 
     private static final String TAG = "BiometricStateCallback";
 
     @NonNull
-    private final CopyOnWriteArrayList<IBiometricStateListener>
-            mBiometricStateListeners = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
+            new CopyOnWriteArrayList<>();
+    @NonNull
+    private final UserManager mUserManager;
+    @BiometricStateListener.State
+    private int mBiometricState;
+    @NonNull
+    private List<T> mProviders = List.of();
 
-    private @BiometricStateListener.State int mBiometricState;
-
-    public BiometricStateCallback() {
+    /**
+     * Create a new callback that must be {@link #start(List)}ed.
+     *
+     * @param userManager user manager
+     */
+    public BiometricStateCallback(@NonNull UserManager userManager) {
         mBiometricState = STATE_IDLE;
+        mUserManager = userManager;
     }
 
+    /**
+     * This should be called when the service has been initialized and all providers are ready.
+     *
+     * @param allProviders all registered biometric service providers
+     */
+    public synchronized void start(@NonNull List<T> allProviders) {
+        mProviders = Collections.unmodifiableList(allProviders);
+        broadcastCurrentEnrollmentState(null /* listener */);
+    }
+
+    /** Get the current state. */
+    @BiometricStateListener.State
     public int getBiometricState() {
         return mBiometricState;
     }
@@ -120,23 +152,43 @@
     }
 
     /**
-     * This should be invoked when:
-     * 1) Enrolled --> None-enrolled
-     * 2) None-enrolled --> enrolled
-     * 3) HAL becomes ready
-     * 4) Listener is registered
+     * Enables clients to register a BiometricStateListener. For example, this is used to forward
+     * fingerprint sensor state changes to SideFpsEventHandler.
+     *
+     * @param listener listener to register
      */
-    public void notifyAllEnrollmentStateChanged(int userId, int sensorId,
+    public synchronized void registerBiometricStateListener(
+            @NonNull IBiometricStateListener listener) {
+        mBiometricStateListeners.add(listener);
+        broadcastCurrentEnrollmentState(listener);
+    }
+
+    private synchronized void broadcastCurrentEnrollmentState(
+            @Nullable IBiometricStateListener listener) {
+        for (T provider : mProviders) {
+            for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
+                for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+                    final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
+                    if (listener != null) {
+                        notifyEnrollmentStateChanged(
+                                listener, userInfo.id, prop.sensorId, enrolled);
+                    } else {
+                        notifyAllEnrollmentStateChanged(
+                                userInfo.id, prop.sensorId, enrolled);
+                    }
+                }
+            }
+        }
+    }
+
+    private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
             boolean hasEnrollments) {
         for (IBiometricStateListener listener : mBiometricStateListeners) {
             notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
         }
     }
 
-    /**
-     * Notifies the listener of enrollment state changes.
-     */
-    public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
+    private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
             int userId, int sensorId, boolean hasEnrollments) {
         try {
             listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
@@ -144,14 +196,4 @@
             Slog.e(TAG, "Remote exception", e);
         }
     }
-
-    /**
-     * Enables clients to register a BiometricStateListener. For example, this is used to forward
-     * fingerprint sensor state changes to SideFpsEventHandler.
-     *
-     * @param listener
-     */
-    public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-        mBiometricStateListeners.add(listener);
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 79e65cc..271bce9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -17,20 +17,18 @@
 package com.android.server.biometrics.sensors.face;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.MANAGE_FACE;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
@@ -39,18 +37,18 @@
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.FaceServiceReceiver;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.face.IFaceService;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.NativeHandle;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -58,10 +56,10 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,51 +86,10 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull
-    private final List<ServiceProvider> mServiceProviders;
-
-    @Nullable
-    private ServiceProvider getProviderForSensor(int sensorId) {
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return provider;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * For devices with only a single provider, returns that provider. If no providers, or multiple
-     * providers exist, returns null.
-     */
-    @Nullable
-    private Pair<Integer, ServiceProvider> getSingleProvider() {
-        final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
-        if (properties.size() != 1) {
-            Slog.e(TAG, "Multiple sensors found: " + properties.size());
-            return null;
-        }
-
-        // Theoretically we can just return the first provider, but maybe this is easier to
-        // understand.
-        final int sensorId = properties.get(0).sensorId;
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return new Pair<>(sensorId, provider);
-            }
-        }
-
-        Slog.e(TAG, "Single sensor, but provider not found");
-        return null;
-    }
-
+    private final FaceServiceRegistry mRegistry;
     @NonNull
-    private List<FaceSensorPropertiesInternal> getSensorProperties() {
-        final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
-        for (ServiceProvider provider : mServiceProviders) {
-            properties.addAll(provider.getSensorProperties());
-        }
-        return properties;
-    }
+    private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
+            mBiometricStateCallback;
 
     /**
      * Receives the incoming binder calls from FaceManager.
@@ -142,8 +99,7 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
                 Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -156,9 +112,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
             final ProtoOutputStream proto = new ProtoOutputStream();
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
                 provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
@@ -170,16 +125,14 @@
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
-
-            return FaceService.this.getSensorProperties();
+            return mRegistry.getAllProperties();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
                         + ", caller: " + opPackageName);
@@ -193,8 +146,7 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFaceServiceReceiver receiver, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
                 return;
@@ -207,8 +159,7 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
                 return;
@@ -222,8 +173,7 @@
         public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
                 return -1;
@@ -245,8 +195,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
                 return;
@@ -260,7 +209,6 @@
         public long authenticate(final IBinder token, final long operationId, int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 boolean isKeyguardBypassEnabled) {
-
             // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
             //  lockdown, something wrong happened. See similar path in FingerprintService.
 
@@ -273,7 +221,7 @@
             // permission.
             final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
@@ -301,7 +249,7 @@
                 return -1;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFace");
                 return -1;
@@ -318,8 +266,7 @@
                 IBinder token, long operationId, int userId,
                 IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
                 int cookie, boolean allowBackgroundAuthentication) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
@@ -336,8 +283,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
                 return;
@@ -350,8 +296,7 @@
         @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName,
                 final long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
                 return;
@@ -370,7 +315,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelFaceDetect");
                 return;
@@ -383,8 +328,7 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
                 return;
@@ -397,8 +341,7 @@
         @Override // Binder call
         public void remove(final IBinder token, final int faceId, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
                 return;
@@ -412,7 +355,6 @@
         @Override // Binder call
         public void removeAll(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-
             final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -432,7 +374,7 @@
 
             // This effectively iterates through all sensors, but has to do so by finding all
             // sensors under each provider.
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
                 for (FaceSensorPropertiesInternal prop : props) {
                     provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver,
@@ -467,27 +409,27 @@
             try {
                 if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpProtoState(props.sensorId, proto, false);
                         }
                     }
                     proto.flush();
                 } else if (args.length > 0 && "--proto".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpProtoMetrics(props.sensorId, fd);
                         }
                     }
                 } else if (args.length > 1 && "--hal".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpHal(props.sensorId, fd,
                                     Arrays.copyOfRange(args, 1, args.length, args.getClass()));
                         }
                     }
                 } else {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             pw.println("Dumping for sensorId: " + props.sensorId
                                     + ", provider: " + provider.getClass().getSimpleName());
@@ -504,10 +446,9 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
             final long token = Binder.clearCallingIdentity();
             try {
-                final ServiceProvider provider = getProviderForSensor(sensorId);
+                final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
                 if (provider == null) {
                     Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
                     return false;
@@ -521,12 +462,11 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName);
                 return Collections.emptyList();
@@ -538,12 +478,11 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName);
                 return false;
@@ -555,8 +494,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
                 return LockoutTracker.LOCKOUT_NONE;
@@ -569,8 +507,7 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
                 return;
@@ -582,7 +519,7 @@
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
                 return 0;
@@ -595,8 +532,7 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
                 String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
                 return;
@@ -610,8 +546,7 @@
         public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
                 final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
                 final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for setFeature");
                 return;
@@ -625,8 +560,7 @@
         @Override
         public void getFeature(final IBinder token, int userId, int feature,
                 IFaceServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getFeature");
                 return;
@@ -636,18 +570,14 @@
                     new ClientMonitorCallbackConverter(receiver), opPackageName);
         }
 
-        private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
-                mServiceProviders.add(
-                        Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
-            }
-        }
+        private List<ServiceProvider> getAidlProviders() {
+            final List<ServiceProvider> providers = new ArrayList<>();
 
-        private void addAidlProviders() {
             final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
             if (instances == null || instances.length == 0) {
-                return;
+                return providers;
             }
+
             for (String instance : instances) {
                 final String fqName = IFace.DESCRIPTOR + "/" + instance;
                 final IFace face = IFace.Stub.asInterface(
@@ -660,53 +590,41 @@
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(), props, instance,
                             mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
-                    mServiceProviders.add(provider);
+                    providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                 }
             }
+
+            return providers;
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-
-            // Some HAL might not be started before the system service and will cause the code below
-            // to wait, and some of the operations below might take a significant amount of time to
-            // complete (calls to the HALs). To avoid blocking the rest of system server we put
-            // this on a background thread.
-            final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /* allowIo */);
-            thread.start();
-            final Handler handler = new Handler(thread.getLooper());
-
-            handler.post(() -> {
-                addHidlProviders(hidlSensors);
-                addAidlProviders();
-
-                final IBiometricService biometricService = IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE));
-
-                // Register each sensor individually with BiometricService
-                for (ServiceProvider provider : mServiceProviders) {
-                    final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
-                    for (FaceSensorPropertiesInternal prop : props) {
-                        final int sensorId = prop.sensorId;
-                        final @BiometricManager.Authenticators.Types int strength =
-                                Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
-                        final FaceAuthenticator authenticator = new FaceAuthenticator(
-                                mServiceWrapper, sensorId);
-                        try {
-                            biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
-                                    authenticator);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
-                        }
-                    }
+            mRegistry.registerAll(() -> {
+                final List<ServiceProvider> providers = new ArrayList<>();
+                for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+                    providers.add(
+                            Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
                 }
+                providers.addAll(getAidlProviders());
+                return providers;
             });
         }
+
+        @Override
+        public void addAuthenticatorsRegisteredCallback(
+                IFaceAuthenticatorsRegisteredCallback callback) {
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mRegistry.addAllRegisteredCallback(callback);
+        }
+
+        @Override
+        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            mBiometricStateCallback.registerBiometricStateListener(listener);
+        }
     }
 
     public FaceService(Context context) {
@@ -714,7 +632,16 @@
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mServiceProviders = new ArrayList<>();
+        mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mRegistry = new FaceServiceRegistry(mServiceWrapper,
+                () -> IBiometricService.Stub.asInterface(
+                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+                mBiometricStateCallback.start(mRegistry.getProviders());
+            }
+        });
     }
 
     @Override
@@ -752,7 +679,7 @@
         if (Utils.isVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                     provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
                             true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
new file mode 100644
index 0000000..0f0a81d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -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.server.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFaceService} providers. */
+public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+        FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> {
+
+    private static final String TAG = "FaceServiceRegistry";
+
+    @NonNull
+    private final IFaceService mService;
+
+    /** Creates a new registry tied to the given service. */
+    public FaceServiceRegistry(@NonNull IFaceService service,
+            @Nullable Supplier<IBiometricService> biometricSupplier) {
+        super(biometricSupplier);
+        mService = service;
+    }
+
+    @Override
+    protected void registerService(@NonNull IBiometricService service,
+            @NonNull FaceSensorPropertiesInternal props) {
+        @BiometricManager.Authenticators.Types final int strength =
+                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+        try {
+            service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+                    new FaceAuthenticator(mService, props.sensorId));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+        }
+    }
+
+    @Override
+    protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback,
+            @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException {
+        callback.onAllAuthenticatorsRegistered(allProps);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 6f98365..4efaedb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -26,15 +26,13 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
 
 import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -56,24 +54,11 @@
  * to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't
  * taken. ServiceProviders will provide a no-op for unsupported operations to fail safely.
  */
-public interface ServiceProvider {
-    /**
-     * Checks if the specified sensor is owned by this provider.
-     */
-    boolean containsSensor(int sensorId);
-
-    @NonNull
-    List<FaceSensorPropertiesInternal> getSensorProperties();
-
-    @NonNull
-    FaceSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> {
 
     @NonNull
     List<Face> getEnrolledFaces(int sensorId, int userId);
 
-    @LockoutTracker.LockoutMode
-    int getLockoutModeForUser(int sensorId, int userId);
-
     /**
      * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be
      * invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -84,10 +69,6 @@
                 + " this method");
     }
 
-    long getAuthenticatorId(int sensorId, int userId);
-
-    boolean isHardwareDetected(int sensorId);
-
     void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, String opPackageName);
 
@@ -142,13 +123,6 @@
     void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
-            boolean clearSchedulerBuffer);
-
-    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
-    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
     @NonNull
     ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 19d54c8..6bff179 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -285,6 +285,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFaces(sensorId, userId).isEmpty();
+    }
+
+    @Override
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 6528912..c0a119f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -484,6 +484,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFaces(sensorId, userId).isEmpty();
+    }
+
+    @Override
     @LockoutTracker.LockoutMode
     public int getLockoutModeForUser(int sensorId, int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2ba449a..7e2742e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -17,14 +17,11 @@
 package com.android.server.biometrics.sensors.fingerprint;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
@@ -36,8 +33,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -65,7 +60,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -79,11 +73,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -115,74 +107,32 @@
 
     protected static final String TAG = "FingerprintService";
 
-    private final Object mLock = new Object();
     private final AppOpsManager mAppOps;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     private final LockPatternUtils mLockPatternUtils;
-    @NonNull private final List<ServiceProvider> mServiceProviders;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final Handler mHandler;
-    @NonNull private final BiometricContext mBiometricContext;
-    @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier;
-    @NonNull private final Function<String, IFingerprint> mIFingerprintProvider;
+    @NonNull
+    private final BiometricContext mBiometricContext;
+    @NonNull
+    private final Supplier<String[]> mAidlInstanceNameSupplier;
+    @NonNull
+    private final Function<String, IFingerprint> mIFingerprintProvider;
+    @NonNull
+    private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
+            mBiometricStateCallback;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final FingerprintServiceRegistry mRegistry;
 
-    @GuardedBy("mLock")
-    @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
-            mAuthenticatorsRegisteredCallbacks;
-
-    @GuardedBy("mLock")
-    @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;
-
-    /**
-     * Registers BiometricStateListener in list stored by FingerprintService
-     * @param listener new BiometricStateListener being added
-     */
-    public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-        mBiometricStateCallback.registerBiometricStateListener(listener);
-        broadcastCurrentEnrollmentState(listener);
-    }
-
-    /**
-     * @param listener if non-null, notifies only this listener. if null, notifies all listeners
-     *                 in {@link BiometricStateCallback}. This is slightly ugly, but reduces
-     *                 redundant code.
-     */
-    private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
-        final UserManager um = UserManager.get(getContext());
-        synchronized (mLock) {
-            // Update the new listener with current state of all sensors
-            for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
-                final ServiceProvider provider = getProviderForSensor(prop.sensorId);
-                for (UserInfo userInfo : um.getAliveUsers()) {
-                    final boolean enrolled = !provider
-                            .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
-
-                    // Defer this work and allow the loop to release the lock sooner
-                    mHandler.post(() -> {
-                        if (listener != null) {
-                            mBiometricStateCallback.notifyEnrollmentStateChanged(
-                                    listener, userInfo.id, prop.sensorId, enrolled);
-                        } else {
-                            mBiometricStateCallback.notifyAllEnrollmentStateChanged(
-                                    userInfo.id, prop.sensorId, enrolled);
-                        }
-                    });
-                }
-            }
-        }
-    }
-
-    /**
-     * Receives the incoming binder calls from FingerprintManager.
-     */
-    private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
+    /** Receives the incoming binder calls from FingerprintManager. */
+    @VisibleForTesting
+    final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
         @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
                 Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -195,9 +145,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
             final ProtoOutputStream proto = new ProtoOutputStream();
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
                 provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
@@ -212,16 +161,14 @@
                     != PackageManager.PERMISSION_GRANTED) {
                 Utils.checkPermission(getContext(), TEST_BIOMETRIC);
             }
-
-            return FingerprintService.this.getSensorProperties();
+            return mRegistry.getAllProperties();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
                         + ", caller: " + opPackageName);
@@ -234,8 +181,7 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFingerprintServiceReceiver receiver, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
                 return;
@@ -248,8 +194,7 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
                 return;
@@ -264,8 +209,7 @@
         public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
                 return -1;
@@ -278,8 +222,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
                 return;
@@ -339,10 +282,10 @@
 
             final Pair<Integer, ServiceProvider> provider;
             if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
-                provider = getSingleProvider();
+                provider = mRegistry.getSingleProvider();
             } else {
                 Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-                provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
+                provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId));
             }
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
@@ -374,7 +317,6 @@
                 final IFingerprintServiceReceiver receiver,
                 final String opPackageName,
                 boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
-
             final Context context = getUiContext();
             final Context promptContext = context.createPackageContextAsUser(
                     opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
@@ -468,7 +410,7 @@
                 return -1;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
                 return -1;
@@ -484,8 +426,7 @@
         public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                 int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
@@ -501,8 +442,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
                 return;
@@ -532,7 +472,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
                 return;
@@ -553,7 +493,7 @@
 
             // For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
             // cancelling authentication.
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelFingerprintDetect");
                 return;
@@ -566,11 +506,9 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
-
-
             Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
                 return;
@@ -583,8 +521,7 @@
         @Override // Binder call
         public void remove(final IBinder token, final int fingerId, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
                 return;
@@ -617,7 +554,7 @@
 
             // This effectively iterates through all sensors, but has to do so by finding all
             // sensors under each provider.
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
                 for (FingerprintSensorPropertiesInternal prop : props) {
                     provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
@@ -652,7 +589,7 @@
             try {
                 if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             provider.dumpProtoState(props.sensorId, proto, false);
@@ -660,14 +597,14 @@
                     }
                     proto.flush();
                 } else if (args.length > 0 && "--proto".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             provider.dumpProtoMetrics(props.sensorId, fd);
                         }
                     }
                 } else {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             pw.println("Dumping for sensorId: " + props.sensorId
@@ -698,7 +635,7 @@
 
             final long token = Binder.clearCallingIdentity();
             try {
-                final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+                final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
                 if (provider == null) {
                     Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
                             + opPackageName);
@@ -713,8 +650,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
                 return false;
@@ -730,7 +666,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for rename");
                 return;
@@ -781,8 +717,7 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
                 return false;
@@ -794,8 +729,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
                 return LockoutTracker.LOCKOUT_NONE;
@@ -807,8 +741,7 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
                 return;
@@ -819,8 +752,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
                 return 0;
@@ -832,8 +764,7 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId,
                 @Nullable byte[] hardwareAuthToken, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
                 return;
@@ -864,55 +795,38 @@
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-
-            // Some HAL might not be started before the system service and will cause the code below
-            // to wait, and some of the operations below might take a significant amount of time to
-            // complete (calls to the HALs). To avoid blocking the rest of system server we put
-            // this on a background thread.
-            final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /* allowIo */);
-            thread.start();
-            final Handler handler = new Handler(thread.getLooper());
-            handler.post(() -> {
+            mRegistry.registerAll(() -> {
+                final List<ServiceProvider> providers = new ArrayList<>();
+                providers.addAll(getHidlProviders(hidlSensors));
                 List<String> aidlSensors = new ArrayList<>();
-                final String[] instances =
-                        ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+                final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
-                registerAuthenticatorsForService(aidlSensors, hidlSensors);
+                providers.addAll(getAidlProviders(
+                        Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+                return providers;
             });
-            thread.quitSafely();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFingerprintAuthenticatorsRegisteredCallback callback) {
-            if (callback == null) {
-                Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
-                return;
-            }
+            mRegistry.addAllRegisteredCallback(callback);
+        }
 
-            final boolean registered;
-            final boolean hasSensorProps;
-            synchronized (mLock) {
-                registered = mAuthenticatorsRegisteredCallbacks.register(callback);
-                hasSensorProps = !mSensorProps.isEmpty();
-            }
-            if (registered && hasSensorProps) {
-                broadcastAllAuthenticatorsRegistered();
-            } else if (!registered) {
-                Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
-            }
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            mBiometricStateCallback.registerBiometricStateListener(listener);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPointerDown(long requestId, int sensorId, int x, int y,
                 float minor, float major) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
                 return;
@@ -923,8 +837,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPointerUp(long requestId, int sensorId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
                 return;
@@ -935,8 +848,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onUiReady(long requestId, int sensorId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
                 return;
@@ -947,8 +859,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
-
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setUdfpsOverlayController(controller);
             }
         }
@@ -956,22 +867,15 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setSidefpsController(@NonNull ISidefpsController controller) {
-
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setSidefpsController(controller);
             }
         }
 
-        @Override
-        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            FingerprintService.this.registerBiometricStateListener(listener);
-        }
-
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPowerPressed() {
-            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.onPowerPressed();
             }
         }
@@ -981,6 +885,7 @@
         this(context, BiometricContext.getInstance(context),
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+                () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
                 (fqName) -> IFingerprint.Stub.asInterface(
                         Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
     }
@@ -988,61 +893,34 @@
     @VisibleForTesting
     FingerprintService(Context context,
             BiometricContext biometricContext,
-            Supplier<IBiometricService> biometricServiceProvider,
+            Supplier<IBiometricService> biometricServiceSupplier,
+            Supplier<String[]> aidlInstanceNameSupplier,
             Function<String, IFingerprint> fingerprintProvider) {
         super(context);
         mBiometricContext = biometricContext;
-        mBiometricServiceSupplier = biometricServiceProvider;
+        mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
         mIFingerprintProvider = fingerprintProvider;
         mAppOps = context.getSystemService(AppOpsManager.class);
         mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mServiceProviders = new ArrayList<>();
-        mBiometricStateCallback = new BiometricStateCallback();
-        mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
-        mSensorProps = new ArrayList<>();
+        mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
         mHandler = new Handler(Looper.getMainLooper());
+        mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                mBiometricStateCallback.start(mRegistry.getProviders());
+            }
+        });
     }
 
-    @VisibleForTesting
-    void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames,
+    @NonNull
+    private List<ServiceProvider> getHidlProviders(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-        addHidlProviders(hidlSensors);
-        addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames));
+        final List<ServiceProvider> providers = new ArrayList<>();
 
-        final IBiometricService biometricService = mBiometricServiceSupplier.get();
-
-        // Register each sensor individually with BiometricService
-        for (ServiceProvider provider : mServiceProviders) {
-            final List<FingerprintSensorPropertiesInternal> props =
-                    provider.getSensorProperties();
-            for (FingerprintSensorPropertiesInternal prop : props) {
-                final int sensorId = prop.sensorId;
-                @BiometricManager.Authenticators.Types final int strength =
-                        Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
-                final FingerprintAuthenticator authenticator = new FingerprintAuthenticator(
-                        mServiceWrapper, sensorId);
-                try {
-                    biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT,
-                            strength, authenticator);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
-                }
-            }
-        }
-
-        synchronized (mLock) {
-            for (ServiceProvider provider : mServiceProviders) {
-                mSensorProps.addAll(provider.getSensorProperties());
-            }
-        }
-
-        broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
-        broadcastAllAuthenticatorsRegistered();
-    }
-
-    private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
         for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
             final Fingerprint21 fingerprint21;
             if ((Build.IS_USERDEBUG || Build.IS_ENG)
@@ -1059,11 +937,16 @@
                         mBiometricStateCallback, hidlSensor, mHandler,
                         mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
             }
-            mServiceProviders.add(fingerprint21);
+            providers.add(fingerprint21);
         }
+
+        return providers;
     }
 
-    private void addAidlProviders(List<String> instances) {
+    @NonNull
+    private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+        final List<ServiceProvider> providers = new ArrayList<>();
+
         for (String instance : instances) {
             final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
             final IFingerprint fp = mIFingerprintProvider.apply(fqName);
@@ -1075,7 +958,7 @@
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
                             mBiometricContext);
                     Slog.i(TAG, "Adding AIDL provider: " + fqName);
-                    mServiceProviders.add(provider);
+                    providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                 }
@@ -1083,38 +966,8 @@
                 Slog.e(TAG, "Unable to get declared service: " + fqName);
             }
         }
-    }
 
-    // Notifies the callbacks that all of the authenticators have been registered and removes the
-    // invoked callbacks from the callback list.
-    private void broadcastAllAuthenticatorsRegistered() {
-        // Make a local copy of the data so it can be used outside of the synchronized block when
-        // making Binder calls.
-        final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
-        final List<FingerprintSensorPropertiesInternal> props;
-        synchronized (mLock) {
-            if (!mSensorProps.isEmpty()) {
-                props = new ArrayList<>(mSensorProps);
-            } else {
-                Slog.e(TAG, "mSensorProps is empty");
-                return;
-            }
-            final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
-            for (int i = 0; i < n; ++i) {
-                final IFingerprintAuthenticatorsRegisteredCallback cb =
-                        mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
-                callbacks.add(cb);
-                mAuthenticatorsRegisteredCallbacks.unregister(cb);
-            }
-            mAuthenticatorsRegisteredCallbacks.finishBroadcast();
-        }
-        for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
-            try {
-                cb.onAllAuthenticatorsRegistered(props);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
-            }
-        }
+        return providers;
     }
 
     @Override
@@ -1122,51 +975,9 @@
         publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
     }
 
-    @Nullable
-    private ServiceProvider getProviderForSensor(int sensorId) {
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return provider;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * For devices with only a single provider, returns that provider. If multiple providers,
-     * returns the first one. If no providers, returns null.
-     */
-    @Nullable
-    private Pair<Integer, ServiceProvider> getSingleProvider() {
-        final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
-        if (properties.isEmpty()) {
-            Slog.e(TAG, "No providers found");
-            return null;
-        }
-
-        // Theoretically we can just return the first provider, but maybe this is easier to
-        // understand.
-        final int sensorId = properties.get(0).sensorId;
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return new Pair<>(sensorId, provider);
-            }
-        }
-
-        Slog.e(TAG, "Provider not found");
-        return null;
-    }
-
-    @NonNull
-    private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
-        synchronized (mLock) {
-            return mSensorProps;
-        }
-    }
-
     @NonNull
     private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
-        final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+        final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
         if (provider == null) {
             Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
                     + opPackageName);
@@ -1229,7 +1040,7 @@
         if (Utils.isVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
                     provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
                             true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
new file mode 100644
index 0000000..33810b7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFingerprintService} providers. */
+public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+        FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> {
+
+    private static final String TAG = "FingerprintServiceRegistry";
+
+    @NonNull
+    private final IFingerprintService mService;
+
+    /** Creates a new registry tied to the given service. */
+    public FingerprintServiceRegistry(@NonNull IFingerprintService service,
+            @Nullable Supplier<IBiometricService> biometricSupplier) {
+        super(biometricSupplier);
+        mService = service;
+    }
+
+    @Override
+    protected void registerService(@NonNull IBiometricService service,
+            @NonNull FingerprintSensorPropertiesInternal props) {
+        @BiometricManager.Authenticators.Types final int strength =
+                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+        try {
+            service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+                    new FingerprintAuthenticator(mService, props.sensorId));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+        }
+    }
+
+    @Override
+    protected void invokeRegisteredCallback(
+            @NonNull IFingerprintAuthenticatorsRegisteredCallback callback,
+            @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException {
+        callback.onAllAuthenticatorsRegistered(allProps);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 275d7e4..9075e7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,14 +28,11 @@
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -59,23 +56,8 @@
  * fail safely.
  */
 @SuppressWarnings("deprecation")
-public interface ServiceProvider {
-    /**
-     * Checks if the specified sensor is owned by this provider.
-     */
-    boolean containsSensor(int sensorId);
-
-    @NonNull
-    List<FingerprintSensorPropertiesInternal> getSensorProperties();
-
-    /**
-     * Returns the internal properties of the specified sensor, if owned by this provider.
-     *
-     * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
-     * @return An object representing the internal properties of the specified sensor.
-     */
-    @Nullable
-    FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends
+        BiometricServiceProvider<FingerprintSensorPropertiesInternal> {
 
     void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
 
@@ -126,16 +108,11 @@
     void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
 
-    boolean isHardwareDetected(int sensorId);
-
     void rename(int sensorId, int fingerId, int userId, @NonNull String name);
 
     @NonNull
     List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
 
-    @LockoutTracker.LockoutMode
-    int getLockoutModeForUser(int sensorId, int userId);
-
     /**
      * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
      * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -143,7 +120,6 @@
     void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback);
 
-    long getAuthenticatorId(int sensorId, int userId);
 
     void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
 
@@ -161,13 +137,6 @@
      */
     void setSidefpsController(@NonNull ISidefpsController controller);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
-            boolean clearSchedulerBuffer);
-
-    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
-    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
     @NonNull
     ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 2dc00520..3fe6332 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -565,6 +565,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+    }
+
+    @Override
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ed482f0..0e6df8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -789,6 +789,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+    }
+
+    @Override
     @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b85d36d..16a060a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -4042,11 +4042,9 @@
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
         if (isCurrentIkev2VpnLocked(packageName)) {
-            // Cache ownerUid to prevent the value being changed after performing VpnRunner.exit().
-            final int ownerUid = mOwnerUID;
+            notifyVpnManagerVpnStopped(packageName, mOwnerUID);
 
             mVpnRunner.exit();
-            notifyVpnManagerVpnStopped(packageName, ownerUid);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a817cea..05d32d1 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -148,6 +148,12 @@
     // The currently accepted nominal ambient light level.
     private float mAmbientLux;
 
+    // The last calculated ambient light level (long time window).
+    private float mSlowAmbientLux;
+
+    // The last calculated ambient light level (short time window).
+    private float mFastAmbientLux;
+
     // The last ambient lux value prior to passing the darkening or brightening threshold.
     private float mPreThresholdLux;
 
@@ -440,6 +446,14 @@
         return mAmbientLux;
     }
 
+    float getSlowAmbientLux() {
+        return mSlowAmbientLux;
+    }
+
+    float getFastAmbientLux() {
+        return mFastAmbientLux;
+    }
+
     private boolean setDisplayPolicy(int policy) {
         if (mDisplayPolicy == policy) {
             return false;
@@ -812,20 +826,20 @@
         // proposed ambient light value since the slow value might be sufficiently far enough away
         // from the fast value to cause a recalculation while its actually just converging on
         // the fast value still.
-        float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
-        float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+        mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+        mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
 
-        if ((slowAmbientLux >= mAmbientBrighteningThreshold
-                && fastAmbientLux >= mAmbientBrighteningThreshold
+        if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+                && mFastAmbientLux >= mAmbientBrighteningThreshold
                 && nextBrightenTransition <= time)
-                || (slowAmbientLux <= mAmbientDarkeningThreshold
-                        && fastAmbientLux <= mAmbientDarkeningThreshold
+                || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+                        && mFastAmbientLux <= mAmbientDarkeningThreshold
                         && nextDarkenTransition <= time)) {
             mPreThresholdLux = mAmbientLux;
-            setAmbientLux(fastAmbientLux);
+            setAmbientLux(mFastAmbientLux);
             if (mLoggingEnabled) {
                 Slog.d(TAG, "updateAmbientLux: "
-                        + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+                        + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
                         + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
                         + "mAmbientLux=" + mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 74ee680..4f3fd64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -66,6 +66,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -708,8 +709,8 @@
     private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
             String suffixFormat, long idNumber) {
 
-        final String suffix = String.format(suffixFormat, idNumber);
-        final String filename = String.format(CONFIG_FILE_FORMAT, suffix);
+        final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
+        final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
         final File filePath = Environment.buildPath(
                 baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
         final DisplayDeviceConfig config = new DisplayDeviceConfig(context);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 67f64a99..e53ac37 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -70,8 +70,6 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
-import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 90e4596..8807e19 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1328,9 +1328,6 @@
         }
 
         final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-        if (autoBrightnessAdjustmentChanged) {
-            mTemporaryAutoBrightnessAdjustment = Float.NaN;
-        }
 
         // Use the autobrightness adjustment override if set.
         final float autoBrightnessAdjustment;
@@ -1643,11 +1640,16 @@
         // brightness cap, RBC state, etc.
         mTempBrightnessEvent.setTime(System.currentTimeMillis());
         mTempBrightnessEvent.setBrightness(brightnessState);
+        mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setReason(mBrightnessReason);
         mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
         mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
         mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
                 | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0));
+        mTempBrightnessEvent.setRbcStrength((mCdsi != null && mCdsi.isReduceBrightColorsActivated())
+                ? mCdsi.getReduceBrightColorsStrength() : -1);
+        mTempBrightnessEvent.setPowerFactor(
+                mPowerRequest.lowPowerMode ? mPowerRequest.screenLowPowerBrightnessFactor : 1.0f);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1656,9 +1658,17 @@
                         == BrightnessReason.REASON_TEMPORARY;
         if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
                 || brightnessAdjustmentFlags != 0) {
+            float lastBrightness = mLastBrightnessEvent.getBrightness();
+            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+            mTempBrightnessEvent.setFastAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1 : mAutomaticBrightnessController.getFastAmbientLux());
+            mTempBrightnessEvent.setSlowAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1 : mAutomaticBrightnessController.getSlowAmbientLux());
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
-
             // Adjustment flags (and user-set flag) only get added after the equality checks since
             // they are transient.
             newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
@@ -1666,6 +1676,9 @@
                     ? BrightnessEvent.FLAG_USER_SET : 0));
             Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
 
+            if (userSetBrightnessChanged) {
+                logManualBrightnessEvent(newEvent);
+            }
             if (mBrightnessEventRingBuffer != null) {
                 mBrightnessEventRingBuffer.append(newEvent);
             }
@@ -2309,14 +2322,15 @@
 
     private void handleSettingsChange(boolean userSwitch) {
         mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         if (userSwitch) {
             // Don't treat user switches as user initiated change.
             setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+            updateAutoBrightnessAdjustment();
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.resetShortTermModel();
             }
         }
-        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         // We don't bother with a pending variable for VR screen brightness since we just
         // immediately adapt to it.
         mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
@@ -2385,6 +2399,7 @@
         }
         mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
         mPendingAutoBrightnessAdjustment = Float.NaN;
+        mTemporaryAutoBrightnessAdjustment = Float.NaN;
         return true;
     }
 
@@ -2753,6 +2768,30 @@
         }
     }
 
+    private void logManualBrightnessEvent(BrightnessEvent event) {
+        float hbmMaxNits =
+                event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+                ? -1f : convertToNits(event.getHbmMax());
+
+        // thermalCapNits set to -1 if not currently capping max brightness
+        float thermalCapNits =
+                event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+                ? -1f : convertToNits(event.getThermalMax());
+
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                convertToNits(event.getInitialBrightness()),
+                convertToNits(event.getBrightness()),
+                event.getSlowAmbientLux(),
+                event.getPhysicalDisplayId(),
+                event.isShortTermModelActive(),
+                event.getPowerFactor(),
+                event.getRbcStrength(),
+                hbmMaxNits,
+                thermalCapNits,
+                event.isAutomaticBrightnessEnabled(),
+                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+    }
+
     private final class DisplayControllerHandler extends Handler {
         DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index d831dbd..70415a3 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -33,17 +33,24 @@
 
     private BrightnessReason mReason = new BrightnessReason();
     private int mDisplayId;
-    private float mLux;
-    private float mPreThresholdLux;
+    private String mPhysicalDisplayId;
     private long mTime;
+    private float mLux;
+    private float mFastAmbientLux;
+    private float mSlowAmbientLux;
+    private float mPreThresholdLux;
+    private float mInitialBrightness;
     private float mBrightness;
     private float mRecommendedBrightness;
     private float mPreThresholdBrightness;
-    private float mHbmMax;
-    private float mThermalMax;
     private int mHbmMode;
+    private float mHbmMax;
+    private int mRbcStrength;
+    private float mThermalMax;
+    private float mPowerFactor;
     private int mFlags;
     private int mAdjustmentFlags;
+    private boolean mAutomaticBrightnessEnabled;
 
     public BrightnessEvent(BrightnessEvent that) {
         copyFrom(that);
@@ -60,37 +67,59 @@
      * @param that BrightnessEvent which is to be copied
      */
     public void copyFrom(BrightnessEvent that) {
+        mReason.set(that.getReason());
         mDisplayId = that.getDisplayId();
+        mPhysicalDisplayId = that.getPhysicalDisplayId();
         mTime = that.getTime();
+        // Lux values
         mLux = that.getLux();
+        mFastAmbientLux = that.getFastAmbientLux();
+        mSlowAmbientLux = that.getSlowAmbientLux();
         mPreThresholdLux = that.getPreThresholdLux();
+        // Brightness values
+        mInitialBrightness = that.getInitialBrightness();
         mBrightness = that.getBrightness();
         mRecommendedBrightness = that.getRecommendedBrightness();
         mPreThresholdBrightness = that.getPreThresholdBrightness();
-        mHbmMax = that.getHbmMax();
-        mThermalMax = that.getThermalMax();
-        mFlags = that.getFlags();
+        // Different brightness modulations
         mHbmMode = that.getHbmMode();
-        mReason.set(that.getReason());
+        mHbmMax = that.getHbmMax();
+        mRbcStrength = that.getRbcStrength();
+        mThermalMax = that.getThermalMax();
+        mPowerFactor = that.getPowerFactor();
+        mFlags = that.getFlags();
         mAdjustmentFlags = that.getAdjustmentFlags();
+        // Auto-brightness setting
+        mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
     }
 
     /**
      * A utility to reset the BrightnessEvent to default values
      */
     public void reset() {
+        mReason.set(null);
         mTime = SystemClock.uptimeMillis();
+        mPhysicalDisplayId = "";
+        // Lux values
+        mLux = 0;
+        mFastAmbientLux = 0;
+        mSlowAmbientLux = 0;
+        mPreThresholdLux = 0;
+        // Brightness values
+        mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mLux = 0;
-        mPreThresholdLux = 0;
         mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mHbmMax = PowerManager.BRIGHTNESS_MAX;
-        mThermalMax = PowerManager.BRIGHTNESS_MAX;
-        mFlags = 0;
+        // Different brightness modulations
         mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
-        mReason.set(null);
+        mHbmMax = PowerManager.BRIGHTNESS_MAX;
+        mRbcStrength = 0;
+        mThermalMax = PowerManager.BRIGHTNESS_MAX;
+        mPowerFactor = 1f;
+        mFlags = 0;
         mAdjustmentFlags = 0;
+        // Auto-brightness setting
+        mAutomaticBrightnessEnabled = true;
     }
 
     /**
@@ -104,23 +133,34 @@
     public boolean equalsMainData(BrightnessEvent that) {
         // This equals comparison purposefully ignores time since it is regularly changing and
         // we don't want to log a brightness event just because the time changed.
-        return mDisplayId == that.mDisplayId
+        return mReason.equals(that.mReason)
+                && mDisplayId == that.mDisplayId
+                && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
+                && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
+                && Float.floatToRawIntBits(mFastAmbientLux)
+                == Float.floatToRawIntBits(that.mFastAmbientLux)
+                && Float.floatToRawIntBits(mSlowAmbientLux)
+                == Float.floatToRawIntBits(that.mSlowAmbientLux)
+                && Float.floatToRawIntBits(mPreThresholdLux)
+                == Float.floatToRawIntBits(that.mPreThresholdLux)
+                && Float.floatToRawIntBits(mInitialBrightness)
+                == Float.floatToRawIntBits(that.mInitialBrightness)
                 && Float.floatToRawIntBits(mBrightness)
                 == Float.floatToRawIntBits(that.mBrightness)
                 && Float.floatToRawIntBits(mRecommendedBrightness)
                 == Float.floatToRawIntBits(that.mRecommendedBrightness)
                 && Float.floatToRawIntBits(mPreThresholdBrightness)
                 == Float.floatToRawIntBits(that.mPreThresholdBrightness)
-                && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
-                && Float.floatToRawIntBits(mPreThresholdLux)
-                == Float.floatToRawIntBits(that.mPreThresholdLux)
-                && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
                 && mHbmMode == that.mHbmMode
+                && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
+                && mRbcStrength == that.mRbcStrength
                 && Float.floatToRawIntBits(mThermalMax)
                 == Float.floatToRawIntBits(that.mThermalMax)
+                && Float.floatToRawIntBits(mPowerFactor)
+                == Float.floatToRawIntBits(that.mPowerFactor)
                 && mFlags == that.mFlags
                 && mAdjustmentFlags == that.mAdjustmentFlags
-                && mReason.equals(that.mReason);
+                && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
     }
 
     /**
@@ -133,16 +173,23 @@
         return (includeTime ? TimeUtils.formatForLogging(mTime) + " - " : "")
                 + "BrightnessEvent: "
                 + "disp=" + mDisplayId
+                + ", physDisp=" + mPhysicalDisplayId
                 + ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
+                + ", initBrt=" + mInitialBrightness
                 + ", rcmdBrt=" + mRecommendedBrightness
                 + ", preBrt=" + mPreThresholdBrightness
                 + ", lux=" + mLux
+                + ", fastLux=" + mFastAmbientLux
+                + ", slowLux=" + mSlowAmbientLux
                 + ", preLux=" + mPreThresholdLux
                 + ", hbmMax=" + mHbmMax
                 + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+                + ", rbcStrength=" + mRbcStrength
                 + ", thrmMax=" + mThermalMax
+                + ", powerFactor=" + mPowerFactor
                 + ", flags=" + flagsToString()
-                + ", reason=" + mReason.toString(mAdjustmentFlags);
+                + ", reason=" + mReason.toString(mAdjustmentFlags)
+                + ", autoBrightness=" + mAutomaticBrightnessEnabled;
     }
 
     @Override
@@ -150,12 +197,20 @@
         return toString(/* includeTime */ true);
     }
 
+    public BrightnessReason getReason() {
+        return mReason;
+    }
+
     public void setReason(BrightnessReason reason) {
         this.mReason = reason;
     }
 
-    public BrightnessReason getReason() {
-        return mReason;
+    public long getTime() {
+        return mTime;
+    }
+
+    public void setTime(long time) {
+        this.mTime = time;
     }
 
     public int getDisplayId() {
@@ -166,6 +221,14 @@
         this.mDisplayId = displayId;
     }
 
+    public String getPhysicalDisplayId() {
+        return mPhysicalDisplayId;
+    }
+
+    public void setPhysicalDisplayId(String mPhysicalDisplayId) {
+        this.mPhysicalDisplayId = mPhysicalDisplayId;
+    }
+
     public float getLux() {
         return mLux;
     }
@@ -174,6 +237,22 @@
         this.mLux = lux;
     }
 
+    public float getFastAmbientLux() {
+        return mFastAmbientLux;
+    }
+
+    public void setFastAmbientLux(float mFastAmbientLux) {
+        this.mFastAmbientLux = mFastAmbientLux;
+    }
+
+    public float getSlowAmbientLux() {
+        return mSlowAmbientLux;
+    }
+
+    public void setSlowAmbientLux(float mSlowAmbientLux) {
+        this.mSlowAmbientLux = mSlowAmbientLux;
+    }
+
     public float getPreThresholdLux() {
         return mPreThresholdLux;
     }
@@ -182,12 +261,12 @@
         this.mPreThresholdLux = preThresholdLux;
     }
 
-    public long getTime() {
-        return mTime;
+    public float getInitialBrightness() {
+        return mInitialBrightness;
     }
 
-    public void setTime(long time) {
-        this.mTime = time;
+    public void setInitialBrightness(float mInitialBrightness) {
+        this.mInitialBrightness = mInitialBrightness;
     }
 
     public float getBrightness() {
@@ -214,6 +293,14 @@
         this.mPreThresholdBrightness = preThresholdBrightness;
     }
 
+    public int getHbmMode() {
+        return mHbmMode;
+    }
+
+    public void setHbmMode(int hbmMode) {
+        this.mHbmMode = hbmMode;
+    }
+
     public float getHbmMax() {
         return mHbmMax;
     }
@@ -222,6 +309,14 @@
         this.mHbmMax = hbmMax;
     }
 
+    public int getRbcStrength() {
+        return mRbcStrength;
+    }
+
+    public void setRbcStrength(int mRbcStrength) {
+        this.mRbcStrength = mRbcStrength;
+    }
+
     public float getThermalMax() {
         return mThermalMax;
     }
@@ -230,12 +325,12 @@
         this.mThermalMax = thermalMax;
     }
 
-    public int getHbmMode() {
-        return mHbmMode;
+    public float getPowerFactor() {
+        return mPowerFactor;
     }
 
-    public void setHbmMode(int hbmMode) {
-        this.mHbmMode = hbmMode;
+    public void setPowerFactor(float mPowerFactor) {
+        this.mPowerFactor = mPowerFactor;
     }
 
     public int getFlags() {
@@ -246,6 +341,10 @@
         this.mFlags = flags;
     }
 
+    public boolean isShortTermModelActive() {
+        return (mFlags & FLAG_USER_SET) != 0;
+    }
+
     public int getAdjustmentFlags() {
         return mAdjustmentFlags;
     }
@@ -254,6 +353,14 @@
         this.mAdjustmentFlags = adjustmentFlags;
     }
 
+    public boolean isAutomaticBrightnessEnabled() {
+        return mAutomaticBrightnessEnabled;
+    }
+
+    public void setAutomaticBrightnessEnabled(boolean mAutomaticBrightnessEnabled) {
+        this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
+    }
+
     private String flagsToString() {
         return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
                 + ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 6e30416..366dfd1 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1515,6 +1515,10 @@
             return mReduceBrightColorsTintController.isActivated();
         }
 
+        public int getReduceBrightColorsStrength() {
+            return mReduceBrightColorsTintController.getStrength();
+        }
+
         /**
          * Gets the computed brightness, in nits, when the reduce bright colors feature is applied
          * at the current strength.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d48acb1..23f4373 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -910,11 +910,14 @@
             final String mImeControlTargetName;
             @Nullable
             final String mImeTargetNameFromWm;
+            @Nullable
+            final String mImeSurfaceParentName;
 
             Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
                     @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
                     boolean inFullscreenMode, String requestWindowName,
-                    @Nullable String imeControlTargetName, @Nullable String imeTargetName) {
+                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+                    @Nullable String imeSurfaceParentName) {
                 mClientState = client;
                 mEditorInfo = editorInfo;
                 mFocusedWindowName = focusedWindowName;
@@ -926,6 +929,7 @@
                 mRequestWindowName = requestWindowName;
                 mImeControlTargetName = imeControlTargetName;
                 mImeTargetNameFromWm = imeTargetName;
+                mImeSurfaceParentName = imeSurfaceParentName;
             }
         }
 
@@ -972,6 +976,9 @@
                 pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
 
                 pw.print(prefix);
+                pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+                pw.print(prefix);
                 pw.print(" editorInfo: ");
                 pw.print(" inputType=" + entry.mEditorInfo.inputType);
                 pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
@@ -4676,7 +4683,8 @@
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
                 mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
-                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
+                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
+                info.imeSurfaceParentName));
     }
 
     @BinderThread
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 3cb3431..ffdb66d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -53,6 +53,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.location.ClientBrokerProto;
 
 import java.util.Collections;
@@ -162,7 +163,7 @@
      * The remote callback interface for this client. This will be set to null whenever the
      * client connection is closed (either explicitly or via binder death).
      */
-    private IContextHubClientCallback mCallbackInterface;
+    private IContextHubClientCallback mContextHubClientCallback;
 
     /*
      * True if the client is still registered with the Context Hub Service, false otherwise.
@@ -184,12 +185,13 @@
      * unregistered.
      */
     @GuardedBy("mWakeLock")
-    private boolean mIsWakeLockActive = true;
+    private boolean mIsWakelockUsable = true;
 
     /*
      * Internal interface used to invoke client callbacks.
      */
-    private interface CallbackConsumer {
+    @VisibleForTesting
+    interface CallbackConsumer {
         void accept(IContextHubClientCallback callback) throws RemoteException;
     }
 
@@ -325,17 +327,24 @@
         }
     }
 
-    private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
-            ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
-            short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
-            ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
-            long nanoAppId, String packageName) {
+    private ContextHubClientBroker(
+            Context context,
+            IContextHubWrapper contextHubProxy,
+            ContextHubClientManager clientManager,
+            ContextHubInfo contextHubInfo,
+            short hostEndPointId,
+            IContextHubClientCallback callback,
+            String attributionTag,
+            ContextHubTransactionManager transactionManager,
+            PendingIntent pendingIntent,
+            long nanoAppId,
+            String packageName) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
         mClientManager = clientManager;
         mAttachedContextHubInfo = contextHubInfo;
         mHostEndPointId = hostEndPointId;
-        mCallbackInterface = callback;
+        mContextHubClientCallback = callback;
         if (pendingIntent == null) {
             mPendingIntentRequest = new PendingIntentRequest();
         } else {
@@ -369,7 +378,7 @@
         sendHostEndpointConnectedEvent();
     }
 
-    /* package */ ContextHubClientBroker(
+    ContextHubClientBroker(
             Context context,
             IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager,
@@ -393,7 +402,7 @@
                 packageName);
     }
 
-    /* package */ ContextHubClientBroker(
+    ContextHubClientBroker(
             Context context,
             IContextHubWrapper contextHubProxy,
             ContextHubClientManager clientManager,
@@ -504,36 +513,50 @@
         }
     }
 
-    /* package */ String getPackageName() {
+    String getPackageName() {
         return mPackage;
     }
 
+    @VisibleForTesting
+    boolean isWakelockUsable() {
+        synchronized (mWakeLock) {
+            return mIsWakelockUsable;
+        }
+    }
+
+    @VisibleForTesting
+    WakeLock getWakeLock() {
+        synchronized (mWakeLock) {
+            return mWakeLock;
+        }
+    }
+
     /**
      * Used to override the attribution tag with a newer value if a PendingIntent broker is
      * retrieved.
      */
-    /* package */ void setAttributionTag(String attributionTag) {
+    void setAttributionTag(String attributionTag) {
         mAttributionTag = attributionTag;
     }
 
     /**
      * @return the attribution tag associated with this broker.
      */
-    /* package */ String getAttributionTag() {
+    String getAttributionTag() {
         return mAttributionTag;
     }
 
     /**
      * @return the ID of the context hub this client is attached to
      */
-    /* package */ int getAttachedContextHubId() {
+    int getAttachedContextHubId() {
         return mAttachedContextHubInfo.getId();
     }
 
     /**
      * @return the host endpoint ID of this client
      */
-    /* package */ short getHostEndPointId() {
+    short getHostEndPointId() {
         return mHostEndPointId;
     }
 
@@ -542,17 +565,19 @@
      *
      * @param message the message that came from a nanoapp
      * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
-     * message
+     *     message
      * @param messagePermissions permissions required to consume the message being delivered. These
-     * permissions are what will be attributed to the client through noteOp.
+     *     permissions are what will be attributed to the client through noteOp.
      */
-    /* package */ void sendMessageToClient(
-            NanoAppMessage message, List<String> nanoappPermissions,
+    void sendMessageToClient(
+            NanoAppMessage message,
+            List<String> nanoappPermissions,
             List<String> messagePermissions) {
         long nanoAppId = message.getNanoAppId();
 
-        int authState = updateNanoAppAuthState(nanoAppId, nanoappPermissions,
-                false /* gracePeriodExpired */);
+        int authState =
+                updateNanoAppAuthState(
+                        nanoAppId, nanoappPermissions, false /* gracePeriodExpired */);
 
         // If in the grace period, the host may not receive any messages containing permissions
         // covered data.
@@ -584,7 +609,7 @@
      *
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
-    /* package */ void onNanoAppLoaded(long nanoAppId) {
+    void onNanoAppLoaded(long nanoAppId) {
         // Check the latest state to see if the loaded nanoapp's permissions changed such that the
         // host app can communicate with it again.
         checkNanoappPermsAsync();
@@ -599,16 +624,14 @@
      *
      * @param nanoAppId the ID of the nanoapp that was unloaded.
      */
-    /* package */ void onNanoAppUnloaded(long nanoAppId) {
+    void onNanoAppUnloaded(long nanoAppId) {
         invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId));
         sendPendingIntent(
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId), nanoAppId);
     }
 
-    /**
-     * Notifies the client of a hub reset event if the connection is open.
-     */
-    /* package */ void onHubReset() {
+    /** Notifies the client of a hub reset event if the connection is open. */
+    void onHubReset() {
         invokeCallback(IContextHubClientCallback::onHubReset);
         sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
 
@@ -622,7 +645,7 @@
      * @param nanoAppId the ID of the nanoapp that aborted
      * @param abortCode the nanoapp specific abort code
      */
-    /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
+    void onNanoAppAborted(long nanoAppId, int abortCode) {
         invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
 
         Supplier<Intent> supplier =
@@ -632,18 +655,19 @@
     }
 
     /**
-     * @param intent    the PendingIntent to compare to
+     * @param intent the PendingIntent to compare to
      * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
      * @return true if the given PendingIntent is currently registered, false otherwise
      */
-    /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
+    boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
         PendingIntent pendingIntent;
         long intentNanoAppId;
         synchronized (this) {
             pendingIntent = mPendingIntentRequest.getPendingIntent();
             intentNanoAppId = mPendingIntentRequest.getNanoAppId();
         }
-        return (pendingIntent != null) && pendingIntent.equals(intent)
+        return (pendingIntent != null)
+                && pendingIntent.equals(intent)
                 && intentNanoAppId == nanoAppId;
     }
 
@@ -652,9 +676,9 @@
      *
      * @throws RemoteException if the client process already died
      */
-    /* package */ void attachDeathRecipient() throws RemoteException {
-        if (mCallbackInterface != null) {
-            mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+    void attachDeathRecipient() throws RemoteException {
+        if (mContextHubClientCallback != null) {
+            mContextHubClientCallback.asBinder().linkToDeath(this, 0 /* flags */);
         }
     }
 
@@ -664,7 +688,7 @@
      * @param permissions list of permissions to check
      * @return true if the client has all of the permissions granted
      */
-    /* package */ boolean hasPermissions(List<String> permissions) {
+    boolean hasPermissions(List<String> permissions) {
         for (String permission : permissions) {
             if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
                 return false;
@@ -678,10 +702,10 @@
      *
      * @param permissions list of permissions covering data the client is about to receive
      * @param noteMessage message that should be noted alongside permissions attribution to
-     * facilitate debugging
+     *     facilitate debugging
      * @return true if client has ability to use all of the provided permissions
      */
-    /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+    boolean notePermissions(List<String> permissions, String noteMessage) {
         for (String permission : permissions) {
             int opCode = AppOpsManager.permissionToOpCode(permission);
             if (opCode != AppOpsManager.OP_NONE) {
@@ -691,8 +715,14 @@
                         return false;
                     }
                 } catch (SecurityException e) {
-                    Log.e(TAG, "SecurityException: noteOp for pkg " + mPackage + " opcode "
-                            + opCode + ": " + e.getMessage());
+                    Log.e(
+                            TAG,
+                            "SecurityException: noteOp for pkg "
+                                    + mPackage
+                                    + " opcode "
+                                    + opCode
+                                    + ": "
+                                    + e.getMessage());
                     return false;
                 }
             }
@@ -704,7 +734,7 @@
     /**
      * @return true if the client is a PendingIntent client that has been cancelled.
      */
-    /* package */ boolean isPendingIntentCancelled() {
+    boolean isPendingIntentCancelled() {
         return mIsPendingIntentCancelled.get();
     }
 
@@ -712,7 +742,7 @@
      * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
      * period.
      */
-    /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+    void handleAuthStateTimerExpiry(long nanoAppId) {
         AuthStateDenialTimer timer;
         synchronized (mMessageChannelNanoappIdMap) {
             timer = mNappToAuthTimerMap.remove(nanoAppId);
@@ -720,7 +750,8 @@
 
         if (timer != null) {
             updateNanoAppAuthState(
-                    nanoAppId, Collections.emptyList() /* nanoappPermissions */,
+                    nanoAppId,
+                    Collections.emptyList() /* nanoappPermissions */,
                     true /* gracePeriodExpired */);
         }
     }
@@ -755,8 +786,10 @@
      *         it should transition to denied
      * @return the latest auth state as of the completion of this method.
      */
-    /* package */ int updateNanoAppAuthState(
-            long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired,
+    int updateNanoAppAuthState(
+            long nanoAppId,
+            List<String> nanoappPermissions,
+            boolean gracePeriodExpired,
             boolean forceDenied) {
         int curAuthState;
         int newAuthState;
@@ -834,13 +867,17 @@
      * @param consumer the consumer specifying the callback to invoke
      */
     private synchronized void invokeCallback(CallbackConsumer consumer) {
-        if (mCallbackInterface != null) {
+        if (mContextHubClientCallback != null) {
             try {
                 acquireWakeLock();
-                consumer.accept(mCallbackInterface);
+                consumer.accept(mContextHubClientCallback);
             } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException while invoking client callback (host endpoint ID = "
-                        + mHostEndPointId + ")", e);
+                Log.e(
+                        TAG,
+                        "RemoteException while invoking client callback (host endpoint ID = "
+                                + mHostEndPointId
+                                + ")",
+                        e);
             }
         }
     }
@@ -879,20 +916,20 @@
      */
     private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
         if (mPendingIntentRequest.hasPendingIntent()) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get());
+            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
         }
     }
 
     /**
      * Sends an intent to any existing PendingIntent
      *
-     * @param supplier  method to create the extra Intent
+     * @param supplier method to create the extra Intent
      * @param nanoAppId the ID of the nanoapp which this event is for
      */
     private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
         if (mPendingIntentRequest.hasPendingIntent()
                 && mPendingIntentRequest.getNanoAppId() == nanoAppId) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get());
+            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
         }
     }
 
@@ -902,7 +939,11 @@
      * @param pendingIntent the PendingIntent
      * @param intent the extra Intent data
      */
-    private void doSendPendingIntent(PendingIntent pendingIntent, Intent intent) {
+    @VisibleForTesting
+    void doSendPendingIntent(
+            PendingIntent pendingIntent,
+            Intent intent,
+            PendingIntent.OnFinished onFinishedCallback) {
         try {
             String requiredPermission = Manifest.permission.ACCESS_CONTEXT_HUB;
             acquireWakeLock();
@@ -910,7 +951,7 @@
                     mContext,
                     /* code= */ 0,
                     intent,
-                    /* onFinished= */ this,
+                    /* onFinished= */ onFinishedCallback,
                     /* handler= */ null,
                     requiredPermission,
                     /* options= */ null);
@@ -934,13 +975,11 @@
         return mRegistered;
     }
 
-    /**
-     * Invoked when a client exits either explicitly or by binder death.
-     */
+    /** Invoked when a client exits either explicitly or by binder death. */
     private synchronized void onClientExit() {
-        if (mCallbackInterface != null) {
-            mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
-            mCallbackInterface = null;
+        if (mContextHubClientCallback != null) {
+            mContextHubClientCallback.asBinder().unlinkToDeath(this, 0 /* flags */);
+            mContextHubClientCallback = null;
         }
         // The client is only unregistered and cleared when there is NOT any PendingIntent
         if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
@@ -1056,7 +1095,7 @@
         Binder.withCleanCallingIdentity(
                 () -> {
                     synchronized (mWakeLock) {
-                        if (mIsWakeLockActive) {
+                        if (mIsWakelockUsable) {
                             mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
                         }
                     }
@@ -1092,7 +1131,7 @@
         Binder.withCleanCallingIdentity(
                 () -> {
                     synchronized (mWakeLock) {
-                        mIsWakeLockActive = false;
+                        mIsWakelockUsable = false;
                         while (mWakeLock.isHeld()) {
                             try {
                                 mWakeLock.release();
diff --git a/services/core/java/com/android/server/location/contexthub/TEST_MAPPING b/services/core/java/com/android/server/location/contexthub/TEST_MAPPING
new file mode 100644
index 0000000..2f6aa53
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/TEST_MAPPING
@@ -0,0 +1,26 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.location.contexthub."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/tests/servicestests/src/com/android/server/location/contexthub"
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e27cbea..bfa8af9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -927,8 +927,9 @@
                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
         }
 
-        userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager,
-                userRecord.mHandler, manager));
+        userRecord.mHandler.sendMessage(
+                obtainMessage(
+                        UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
     }
 
     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
@@ -1311,6 +1312,36 @@
                 new CopyOnWriteArrayList<>();
         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
 
+        /**
+         * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
+         * {@link android.media.MediaRouter2Manager managers}.
+         *
+         * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
+         * that have {@code MODIFY_AUDIO_ROUTING} permission.
+         *
+         * <p>This list contains all routes exposed by route providers. This includes routes from
+         * both system route providers and user route providers.
+         *
+         * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+         */
+        private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
+                new ArrayMap<>();
+
+        /**
+         * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
+         *
+         * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
+         * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
+         *
+         * <p>This list contains all routes exposed by user route providers. It might also include
+         * the current default route from {@link #mSystemProvider} to expose local route updates
+         * (e.g. volume changes) to non-privileged routers.
+         *
+         * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
+         */
+        private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
+                new ArrayMap<>();
+
         private boolean mRunning;
 
         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
@@ -1425,91 +1456,182 @@
         }
 
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
-            int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
-            MediaRoute2ProviderInfo prevInfo =
-                    (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
-            if (Objects.equals(prevInfo, currentInfo)) return;
 
-            List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-            List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-            List<MediaRoute2Info> changedRoutes = new ArrayList<>();
+            int providerInfoIndex =
+                    indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
+
+            MediaRoute2ProviderInfo prevInfo =
+                    providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+
+            // Ignore if no changes
+            if (Objects.equals(prevInfo, currentInfo)) {
+                return;
+            }
+
+            boolean hasAddedOrModifiedRoutes = false;
+            boolean hasRemovedRoutes = false;
+
+            boolean isSystemProvider = provider.mIsSystemRouteProvider;
+
             if (prevInfo == null) {
+                // Provider is being added.
                 mLastProviderInfos.add(currentInfo);
-                addedRoutes.addAll(currentInfo.getRoutes());
+                addToRoutesMap(currentInfo.getRoutes(), isSystemProvider);
+                // Check if new provider exposes routes.
+                hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty();
             } else if (currentInfo == null) {
+                // Provider is being removed.
+                hasRemovedRoutes = true;
                 mLastProviderInfos.remove(prevInfo);
-                removedRoutes.addAll(prevInfo.getRoutes());
+                removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider);
             } else {
+                // Provider is being updated.
                 mLastProviderInfos.set(providerInfoIndex, currentInfo);
-                final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
                 final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
 
+                // Checking for individual routes.
                 for (MediaRoute2Info route : currentRoutes) {
                     if (!route.isValid()) {
-                        Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
-                                + route);
+                        Slog.w(
+                                TAG,
+                                "onProviderStateChangedOnHandler: Ignoring invalid route : "
+                                        + route);
                         continue;
                     }
+
                     MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
-                    if (prevRoute == null) {
-                        addedRoutes.add(route);
-                    } else if (!Objects.equals(prevRoute, route)) {
-                        changedRoutes.add(route);
+                    if (prevRoute == null || !Objects.equals(prevRoute, route)) {
+                        hasAddedOrModifiedRoutes = true;
+                        mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+                        if (!isSystemProvider) {
+                            mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+                        }
                     }
                 }
 
+                // Checking for individual removals
                 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
                     if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
-                        removedRoutes.add(prevRoute);
+                        hasRemovedRoutes = true;
+                        mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId());
+                        if (!isSystemProvider) {
+                            mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId());
+                        }
                     }
                 }
             }
 
+            dispatchUpdates(
+                    hasAddedOrModifiedRoutes,
+                    hasRemovedRoutes,
+                    isSystemProvider,
+                    mSystemProvider.getDefaultRoute());
+        }
+
+        /**
+         * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them
+         * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a
+         * non-system route provider. Overwrites any route with matching id that already exists.
+         *
+         * @param routes list of routes to be added.
+         * @param isSystemRoutes indicates whether routes come from a system route provider.
+         */
+        private void addToRoutesMap(
+                @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+            for (MediaRoute2Info route : routes) {
+                if (!isSystemRoutes) {
+                    mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+                }
+                mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+            }
+        }
+
+        /**
+         * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also
+         * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were
+         * provided by a non-system route provider.
+         *
+         * @param routes list of routes to be removed.
+         * @param isSystemRoutes whether routes come from a system route provider.
+         */
+        private void removeFromRoutesMap(
+                @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+            for (MediaRoute2Info route : routes) {
+                if (!isSystemRoutes) {
+                    mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId());
+                }
+                mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId());
+            }
+        }
+
+        /**
+         * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
+         * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
+         * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
+         * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
+         * were made.
+         *
+         * @param hasAddedOrModifiedRoutes whether routes were added or modified.
+         * @param hasRemovedRoutes whether routes were removed.
+         * @param isSystemProvider whether the latest update was caused by a system provider.
+         * @param defaultRoute the current default route in {@link #mSystemProvider}.
+         */
+        private void dispatchUpdates(
+                boolean hasAddedOrModifiedRoutes,
+                boolean hasRemovedRoutes,
+                boolean isSystemProvider,
+                MediaRoute2Info defaultRoute) {
+
+            // Ignore if no changes.
+            if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
+                return;
+            }
+
             List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
             List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
             List<IMediaRouter2Manager> managers = getManagers();
-            List<MediaRoute2Info> defaultRoute = new ArrayList<>();
-            defaultRoute.add(mSystemProvider.getDefaultRoute());
 
-            if (addedRoutes.size() > 0) {
-                notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            addedRoutes);
-                } else if (prevInfo == null) {
-                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            defaultRoute);
-                } // 'else' is handled as changed routes
-                notifyRoutesAddedToManagers(managers, addedRoutes);
-            }
-            if (removedRoutes.size() > 0) {
-                notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
-                        removedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            removedRoutes);
-                }
-                notifyRoutesRemovedToManagers(managers, removedRoutes);
-            }
-            if (changedRoutes.size() > 0) {
-                notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
-                        changedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            changedRoutes);
-                } else if (prevInfo != null) {
-                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            defaultRoute);
-                } // 'else' is handled as added routes
-                notifyRoutesChangedToManagers(managers, changedRoutes);
+            // Managers receive all provider updates with all routes.
+            notifyRoutesUpdatedToManagers(
+                    managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+            // Routers with modify audio permission (usually system routers) receive all provider
+            // updates with all routes.
+            notifyRoutesUpdatedToRouters(
+                    routersWithModifyAudioRoutingPermission,
+                    new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+            if (!isSystemProvider) {
+                // Regular routers receive updates from all non-system providers with all non-system
+                // routes.
+                notifyRoutesUpdatedToRouters(
+                        routersWithoutModifyAudioRoutingPermission,
+                        new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+            } else if (hasAddedOrModifiedRoutes) {
+                // On system provider updates, regular routers receive the updated default route.
+                // This is the only system route they should receive.
+                mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
+                notifyRoutesUpdatedToRouters(
+                        routersWithoutModifyAudioRoutingPermission,
+                        new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             }
         }
 
-        private int getLastProviderInfoIndex(@NonNull String providerId) {
-            for (int i = 0; i < mLastProviderInfos.size(); i++) {
-                MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
-                if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
+        /**
+         * Returns the index of the first element in {@code lastProviderInfos} that matches the
+         * specified unique id.
+         *
+         * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
+         * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
+         * @return index of found element, or -1 if not found.
+         */
+        private static int indexOfRouteProviderInfoByUniqueId(
+                @NonNull String uniqueId,
+                @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
+            for (int i = 0; i < lastProviderInfos.size(); i++) {
+                MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
+                if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
                     return i;
                 }
             }
@@ -1989,41 +2111,19 @@
             }
         }
 
-        private void notifyRoutesAddedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
+        private void notifyRoutesUpdatedToRouters(
+                @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
             for (IMediaRouter2 router : routers) {
                 try {
-                    router.notifyRoutesAdded(routes);
+                    router.notifyRoutesUpdated(routes);
                 } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex);
+                    Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
                 }
             }
         }
 
-        private void notifyRoutesRemovedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2 router : routers) {
-                try {
-                    router.notifyRoutesRemoved(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesChangedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2 router : routers) {
-                try {
-                    router.notifyRoutesChanged(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex);
-                }
-            }
-        }
-
-        private void notifySessionInfoChangedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull RoutingSessionInfo sessionInfo) {
+        private void notifySessionInfoChangedToRouters(
+                @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
             for (IMediaRouter2 router : routers) {
                 try {
                     router.notifySessionInfoChanged(sessionInfo);
@@ -2033,48 +2133,31 @@
             }
         }
 
-        private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) {
-            List<MediaRoute2Info> routes = new ArrayList<>();
-            for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
-                routes.addAll(providerInfo.getRoutes());
-            }
-            if (routes.size() == 0) {
+        /**
+         * Notifies {@code manager} with all known routes. This only happens once after {@code
+         * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
+         * registerManager()}.
+         *
+         * @param manager {@link IMediaRouter2Manager} to be notified.
+         */
+        private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
+            if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
                 return;
             }
             try {
-                manager.notifyRoutesAdded(routes);
+                manager.notifyRoutesUpdated(
+                        new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
             }
         }
 
-        private void notifyRoutesAddedToManagers(@NonNull List<IMediaRouter2Manager> managers,
+        private void notifyRoutesUpdatedToManagers(
+                @NonNull List<IMediaRouter2Manager> managers,
                 @NonNull List<MediaRoute2Info> routes) {
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifyRoutesAdded(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesRemovedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2Manager manager : managers) {
-                try {
-                    manager.notifyRoutesRemoved(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesChangedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2Manager manager : managers) {
-                try {
-                    manager.notifyRoutesChanged(routes);
+                    manager.notifyRoutesUpdated(routes);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
                 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7d12ede..ed8d852 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -335,8 +335,8 @@
 
         @Override // Binder call
         public void stopActiveProjection() {
-            if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
-                        != PackageManager.PERMISSION_GRANTED) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+                    != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
                         + "projection callbacks");
             }
@@ -393,9 +393,14 @@
                     if (!isValidMediaProjection(projection)) {
                         throw new SecurityException("Invalid media projection");
                     }
-                    LocalServices.getService(
+                    if (!LocalServices.getService(
                             WindowManagerInternal.class).setContentRecordingSession(
-                            incomingSession);
+                            incomingSession)) {
+                        // Unable to start mirroring, so tear down this projection.
+                        if (mProjectionGrant != null) {
+                            mProjectionGrant.stop();
+                        }
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
index fde45f71..acb36a0 100644
--- a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -42,10 +42,6 @@
      */
     public static void scheduleJob(Context context, long rescheduleTimeMillis) {
         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-        // if the job already exists for some reason, cancel & reschedule
-        if (jobScheduler.getPendingJob(JOB_ID) != null) {
-            jobScheduler.cancel(JOB_ID);
-        }
         ComponentName component = new ComponentName(
                 context, ReviewNotificationPermissionsJobService.class);
         JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72f3850..e9637ec 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6309,21 +6309,6 @@
             }
         }
 
-        /**
-         * Ask the package manager to compile layouts in the given package.
-         */
-        @Override
-        public boolean compileLayouts(String packageName) {
-            AndroidPackage pkg;
-            synchronized (mLock) {
-                pkg = mPackages.get(packageName);
-                if (pkg == null) {
-                    return false;
-                }
-            }
-            return mArtManagerService.compileLayouts(pkg);
-        }
-
         @Nullable
         @Override
         public String removeLegacyDefaultBrowserPackageName(int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dba7dcd..da89b9e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1792,7 +1792,6 @@
         String checkProfilesRaw = null;
         boolean secondaryDex = false;
         String split = null;
-        boolean compileLayouts = false;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -1812,9 +1811,6 @@
                 case "-r":
                     compilationReason = getNextArgRequired();
                     break;
-                case "--compile-layouts":
-                    compileLayouts = true;
-                    break;
                 case "--check-prof":
                     checkProfilesRaw = getNextArgRequired();
                     break;
@@ -1848,14 +1844,15 @@
 
         final boolean compilerFilterGiven = compilerFilter != null;
         final boolean compilationReasonGiven = compilationReason != null;
-        // Make sure exactly one of -m, -r, or --compile-layouts is given.
-        if ((!compilerFilterGiven && !compilationReasonGiven && !compileLayouts)
-            || (!compilerFilterGiven && compilationReasonGiven && compileLayouts)
-            || (compilerFilterGiven && !compilationReasonGiven && compileLayouts)
-            || (compilerFilterGiven && compilationReasonGiven && !compileLayouts)
-            || (compilerFilterGiven && compilationReasonGiven && compileLayouts)) {
-            pw.println("Must specify exactly one of compilation filter (\"-m\"), compilation " +
-                    "reason (\"-r\"), or compile layouts (\"--compile-layouts\")");
+        // Make sure exactly one of -m, or -r is given.
+        if (compilerFilterGiven && compilationReasonGiven) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
+                    + "at the same time");
+            return 1;
+        }
+        if (!compilerFilterGiven && !compilationReasonGiven) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
+                    + "reason (\"-r\")");
             return 1;
         }
 
@@ -1920,19 +1917,12 @@
                 pw.flush();
             }
 
-            boolean result = true;
-            if (compileLayouts) {
-                PackageManagerInternal internal = LocalServices.getService(
-                        PackageManagerInternal.class);
-                result = internal.compileLayouts(packageName);
-            } else {
-                result = secondaryDex
+            final boolean result = secondaryDex
                     ? mInterface.performDexOptSecondary(packageName,
                             targetCompilerFilter, forceCompilation)
                     : mInterface.performDexOptMode(packageName,
                             checkProfiles, targetCompilerFilter, forceCompilation,
                             true /* bootComplete */, split);
-            }
             if (!result) {
                 failedPackages.add(packageName);
             }
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index a574fc9..8acdb0e 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -31,6 +31,7 @@
     private final SparseBooleanArray mSufficientVerifierUids;
 
     private final SparseBooleanArray mRequiredVerifierUids;
+    private final SparseBooleanArray mUnrespondedRequiredVerifierUids;
 
     private boolean mSufficientVerificationComplete;
 
@@ -52,6 +53,7 @@
         mVerifyingSession = verifyingSession;
         mSufficientVerifierUids = new SparseBooleanArray();
         mRequiredVerifierUids = new SparseBooleanArray();
+        mUnrespondedRequiredVerifierUids = new SparseBooleanArray();
         mRequiredVerificationComplete = false;
         mRequiredVerificationPassed = true;
         mExtendedTimeout = false;
@@ -64,6 +66,7 @@
     /** Add the user ID of the required package verifier. */
     void addRequiredVerifierUid(int uid) {
         mRequiredVerifierUids.put(uid, true);
+        mUnrespondedRequiredVerifierUids.put(uid, true);
     }
 
     /** Returns true if the uid a required verifier. */
@@ -103,8 +106,8 @@
                     mRequiredVerificationPassed = false;
             }
 
-            mRequiredVerifierUids.delete(uid);
-            if (mRequiredVerifierUids.size() == 0) {
+            mUnrespondedRequiredVerifierUids.delete(uid);
+            if (mUnrespondedRequiredVerifierUids.size() == 0) {
                 mRequiredVerificationComplete = true;
             }
             return true;
@@ -129,7 +132,7 @@
      * Mark the session as passed required verification.
      */
     void passRequiredVerification() {
-        if (mRequiredVerifierUids.size() > 0) {
+        if (mUnrespondedRequiredVerifierUids.size() > 0) {
             throw new RuntimeException("Required verifiers still present.");
         }
         mRequiredVerificationPassed = true;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 5b8f473..c129f37 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1256,6 +1256,18 @@
         return input.success(pkg.addPermission(result.getResult()));
     }
 
+    private int parseMinOrMaxSdkVersion(TypedArray sa, int attr, int defaultValue) {
+        int val = defaultValue;
+        TypedValue peekVal = sa.peekValue(attr);
+        if (peekVal != null) {
+            if (peekVal.type >= TypedValue.TYPE_FIRST_INT
+                    && peekVal.type <= TypedValue.TYPE_LAST_INT) {
+                val = peekVal.data;
+            }
+        }
+        return val;
+    }
+
     private ParseResult<ParsingPackage> parseUsesPermission(ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws IOException, XmlPullParserException {
@@ -1266,14 +1278,13 @@
             String name = sa.getNonResourceString(
                     R.styleable.AndroidManifestUsesPermission_name);
 
-            int maxSdkVersion = 0;
-            TypedValue val = sa.peekValue(
-                    R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
-            if (val != null) {
-                if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
-                    maxSdkVersion = val.data;
-                }
-            }
+            int minSdkVersion =  parseMinOrMaxSdkVersion(sa,
+                    R.styleable.AndroidManifestUsesPermission_minSdkVersion,
+                    Integer.MIN_VALUE);
+
+            int maxSdkVersion =  parseMinOrMaxSdkVersion(sa,
+                    R.styleable.AndroidManifestUsesPermission_maxSdkVersion,
+                    Integer.MAX_VALUE);
 
             final ArraySet<String> requiredFeatures = new ArraySet<>();
             String feature = sa.getNonConfigurationString(
@@ -1338,7 +1349,8 @@
                 return success;
             }
 
-            if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+            if (Build.VERSION.RESOURCES_SDK_INT < minSdkVersion
+                    || Build.VERSION.RESOURCES_SDK_INT > maxSdkVersion) {
                 return success;
             }
 
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index af2b902..8582f54 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -23,11 +23,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
@@ -35,12 +32,11 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Build;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
+import android.view.View;
+import android.view.Window;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -48,49 +44,48 @@
 
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
 
 /**
  * Defines behavior for handling interactions between power button events and fingerprint-related
  * operations, for devices where the fingerprint sensor (side fps) lives on the power button.
  */
-public class SideFpsEventHandler {
+public class SideFpsEventHandler implements View.OnClickListener {
 
     private static final int DEBOUNCE_DELAY_MILLIS = 500;
 
-    private int getTapWaitForPowerDuration(Context context) {
-        int tap = context.getResources().getInteger(
-                R.integer.config_sidefpsEnrollPowerPressWindow);
-        if (Build.isDebuggable()) {
-            tap = Settings.Secure.getIntForUser(context.getContentResolver(),
-                    Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
-                    UserHandle.USER_CURRENT);
-        }
-        return tap;
-    }
-
     private static final String TAG = "SideFpsEventHandler";
 
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final PowerManager mPowerManager;
-    @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier;
-    @NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
-
-    @Nullable private Dialog mDialog;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final PowerManager mPowerManager;
+    @NonNull
+    private final AtomicBoolean mSideFpsEventHandlerReady;
+    private final int mDismissDialogTimeout;
+    @Nullable
+    private SideFpsToast mDialog;
     private final Runnable mTurnOffDialog =
             () -> {
                 dismissDialog("mTurnOffDialog");
             };
-
-    @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
-
     private @BiometricStateListener.State int mBiometricState;
-    private final int mTapWaitForPowerDuration;
     private FingerprintManager mFingerprintManager;
+    private DialogProvider mDialogProvider;
+    private long mLastPowerPressTime;
 
-    SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
-        this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
+    SideFpsEventHandler(
+            Context context,
+            Handler handler,
+            PowerManager powerManager) {
+        this(context, handler, powerManager, (ctx) -> {
+            SideFpsToast dialog = new SideFpsToast(ctx);
+            dialog.getWindow()
+                    .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+            dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+            return dialog;
+        });
     }
 
     @VisibleForTesting
@@ -98,23 +93,13 @@
             Context context,
             Handler handler,
             PowerManager powerManager,
-            Supplier<AlertDialog.Builder> dialogSupplier) {
+            DialogProvider provider) {
         mContext = context;
         mHandler = handler;
         mPowerManager = powerManager;
-        mDialogSupplier = dialogSupplier;
         mBiometricState = STATE_IDLE;
         mSideFpsEventHandlerReady = new AtomicBoolean(false);
-        mDialogDismissListener =
-                (dialog) -> {
-                    if (mDialog == dialog) {
-                        if (mHandler != null) {
-                            mHandler.removeCallbacks(mTurnOffDialog);
-                        }
-                        mDialog = null;
-                    }
-                };
-
+        mDialogProvider = provider;
         // ensure dialog is dismissed if screen goes off for unrelated reasons
         context.registerReceiver(
                 new BroadcastReceiver() {
@@ -127,7 +112,13 @@
                     }
                 },
                 new IntentFilter(Intent.ACTION_SCREEN_OFF));
-        mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
+        mDismissDialogTimeout = context.getResources().getInteger(
+                R.integer.config_sideFpsToastTimeout);
+    }
+
+    @Override
+    public void onClick(View v) {
+        goToSleep(mLastPowerPressTime);
     }
 
     /**
@@ -165,8 +156,9 @@
                                 Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
                                 mHandler.removeCallbacks(mTurnOffDialog);
                             }
+                            showDialog(eventTime, "Enroll Power Press");
+                            mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
                         });
-                showDialog(eventTime, "Enroll Power Press");
                 return true;
             case STATE_BP_AUTH:
                 return true;
@@ -176,54 +168,11 @@
         }
     }
 
-    @NonNull
-    private static Dialog showConfirmDialog(
-            @NonNull AlertDialog.Builder dialogBuilder,
-            @NonNull PowerManager powerManager,
-            long eventTime,
-            @BiometricStateListener.State int biometricState,
-            @NonNull DialogInterface.OnDismissListener dismissListener) {
-        final boolean enrolling = biometricState == STATE_ENROLLING;
-        final int title =
-                enrolling
-                        ? R.string.fp_power_button_enrollment_title
-                        : R.string.fp_power_button_bp_title;
-        final int message =
-                enrolling
-                        ? R.string.fp_power_button_enrollment_message
-                        : R.string.fp_power_button_bp_message;
-        final int positiveText =
-                enrolling
-                        ? R.string.fp_power_button_enrollment_positive_button
-                        : R.string.fp_power_button_bp_positive_button;
-        final int negativeText =
-                enrolling
-                        ? R.string.fp_power_button_enrollment_negative_button
-                        : R.string.fp_power_button_bp_negative_button;
-
-        final Dialog confirmScreenOffDialog =
-                dialogBuilder
-                        .setTitle(title)
-                        .setMessage(message)
-                        .setPositiveButton(
-                                positiveText,
-                                (dialog, which) -> {
-                                    dialog.dismiss();
-                                    powerManager.goToSleep(
-                                            eventTime,
-                                            PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
-                                            0 /* flags */);
-                                })
-                        .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
-                        .setOnDismissListener(dismissListener)
-                        .setCancelable(false)
-                        .create();
-        confirmScreenOffDialog
-                .getWindow()
-                .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
-        confirmScreenOffDialog.show();
-
-        return confirmScreenOffDialog;
+    private void goToSleep(long eventTime) {
+        mPowerManager.goToSleep(
+                eventTime,
+                PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+                0 /* flags */);
     }
 
     /**
@@ -247,7 +196,8 @@
                         if (fingerprintManager.isPowerbuttonFps()) {
                             fingerprintManager.registerBiometricStateListener(
                                     new BiometricStateListener() {
-                                        @Nullable private Runnable mStateRunnable = null;
+                                        @Nullable
+                                        private Runnable mStateRunnable = null;
 
                                         @Override
                                         public void onStateChanged(
@@ -281,13 +231,6 @@
                                         public void onBiometricAction(
                                                 @BiometricStateListener.Action int action) {
                                             Log.d(TAG, "onBiometricAction " + action);
-                                            switch (action) {
-                                                case BiometricStateListener.ACTION_SENSOR_TOUCH:
-                                                    mHandler.postDelayed(
-                                                            mTurnOffDialog,
-                                                            mTapWaitForPowerDuration);
-                                                    break;
-                                            }
                                         }
                                     });
                             mSideFpsEventHandlerReady.set(true);
@@ -309,12 +252,13 @@
             Log.d(TAG, "Ignoring show dialog");
             return;
         }
-        mDialog =
-                showConfirmDialog(
-                        mDialogSupplier.get(),
-                        mPowerManager,
-                        time,
-                        mBiometricState,
-                        mDialogDismissListener);
+        mDialog = mDialogProvider.provideDialog(mContext);
+        mLastPowerPressTime = time;
+        mDialog.show();
+        mDialog.setOnClickListener(this);
     }
-}
+
+    interface DialogProvider {
+        SideFpsToast provideDialog(Context context);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
new file mode 100644
index 0000000..db07467
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SideFpsToast.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import com.android.internal.R;
+
+/**
+ * Toast for side fps. This is typically shown during enrollment
+ * when a user presses the power button.
+ *
+ * This dialog is used by {@link SideFpsEventHandler}
+ */
+public class SideFpsToast extends Dialog {
+
+    SideFpsToast(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.side_fps_toast);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        final Window window = this.getWindow();
+        WindowManager.LayoutParams windowParams = window.getAttributes();
+        windowParams.dimAmount = 0;
+        windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+        windowParams.gravity = Gravity.BOTTOM;
+        window.setAttributes(windowParams);
+    }
+
+    /**
+     * Sets the onClickListener for the toast dialog.
+     * @param listener
+     */
+    public void setOnClickListener(View.OnClickListener listener) {
+        final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+        if (turnOffScreen != null) {
+            turnOffScreen.setOnClickListener(listener);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a21919c..d48af21 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1,5 +1,6 @@
 package com.android.server.wm;
 
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.processStateAmToProto;
@@ -274,6 +275,8 @@
         final boolean mProcessRunning;
         /** whether the process of the launching activity didn't have any active activity. */
         final boolean mProcessSwitch;
+        /** The process state of the launching activity prior to the launch */
+        final int mProcessState;
         /** Whether the last launched activity has reported drawn. */
         boolean mIsDrawn;
         /** The latest activity to have been launched. */
@@ -309,8 +312,8 @@
         @Nullable
         static TransitionInfo create(@NonNull ActivityRecord r,
                 @NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
-                boolean processRunning, boolean processSwitch, boolean newActivityCreated,
-                int startResult) {
+                boolean processRunning, boolean processSwitch, int processState,
+                boolean newActivityCreated, int startResult) {
             if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
                 return null;
             }
@@ -325,18 +328,19 @@
                 transitionType = TYPE_TRANSITION_COLD_LAUNCH;
             }
             return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
-                    processSwitch);
+                    processSwitch, processState);
         }
 
         /** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
         private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
                 ActivityOptions options, int transitionType, boolean processRunning,
-                boolean processSwitch) {
+                boolean processSwitch, int processState) {
             mLaunchingState = launchingState;
             mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
             mTransitionType = transitionType;
             mProcessRunning = processRunning;
             mProcessSwitch = processSwitch;
+            mProcessState = processState;
             mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
             setLatestLaunchedActivity(r);
             // The launching state can be reused by consecutive launch. Its original association
@@ -640,12 +644,16 @@
         // interesting.
         final boolean processSwitch = !processRunning
                 || !processRecord.hasStartedActivity(launchedActivity);
+        final int processState = processRunning
+                ? processRecord.getCurrentProcState()
+                : PROCESS_STATE_NONEXISTENT;
 
         final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
         if (DEBUG_METRICS) {
             Slog.i(TAG, "notifyActivityLaunched" + " resultCode=" + resultCode
                     + " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
                     + " processSwitch=" + processSwitch
+                    + " processState=" + processState
                     + " newActivityCreated=" + newActivityCreated + " info=" + info);
         }
 
@@ -681,7 +689,8 @@
         }
 
         final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
-                options, processRunning, processSwitch, newActivityCreated, resultCode);
+                options, processRunning, processSwitch, processState, newActivityCreated,
+                resultCode);
         if (newInfo == null) {
             abort(launchingState, "unrecognized launch");
             return;
@@ -996,8 +1005,9 @@
             final long timestamp = info.mTransitionStartTimeNs;
             final long uptime = info.mTransitionDeviceUptimeMs;
             final int transitionDelay = info.mCurrentTransitionDelayMs;
+            final int processState = info.mProcessState;
             mLoggerHandler.post(() -> logAppTransition(
-                    timestamp, uptime, transitionDelay, infoSnapshot, isHibernating));
+                    timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, processState));
         }
         mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
@@ -1009,7 +1019,8 @@
 
     // This gets called on another thread without holding the activity manager lock.
     private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
-            int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating) {
+            int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
+            int processState) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
         builder.setPackageName(info.packageName);
         builder.setType(info.type);
@@ -1075,7 +1086,8 @@
                 isIncremental,
                 isLoading,
                 info.launchedActivityName.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs));
+                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+                processState);
 
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 135cf5d..c64e525 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -78,6 +78,11 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -336,6 +341,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
@@ -6734,7 +6740,7 @@
      * @param windowPid The pid of the window input dispatching timed out on.
      * @return True if input dispatching should be aborted.
      */
-    public boolean inputDispatchingTimedOut(String reason, int windowPid) {
+    public boolean inputDispatchingTimedOut(TimeoutRecord timeoutRecord, int windowPid) {
         ActivityRecord anrActivity;
         WindowProcessController anrApp;
         boolean blameActivityProcess;
@@ -6748,12 +6754,12 @@
         if (blameActivityProcess) {
             return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
                     anrActivity.shortComponentName, anrActivity.info.applicationInfo,
-                    shortComponentName, app, false, reason);
+                    shortComponentName, app, false, timeoutRecord);
         } else {
             // In this case another process added windows using this activity token. So, we call the
             // generic service input dispatch timed out method so that the right process is blamed.
             long timeoutMillis = mAtmService.mAmInternal.inputDispatchingTimedOut(
-                    windowPid, false /* aboveSystem */, reason);
+                    windowPid, false /* aboveSystem */, timeoutRecord);
             return timeoutMillis <= 0;
         }
     }
@@ -8750,15 +8756,35 @@
     /**
      * Returns the min aspect ratio of this activity.
      */
-    private float getMinAspectRatio() {
-        return info.getMinAspectRatio(getRequestedOrientation());
+    float getMinAspectRatio() {
+        if (info.applicationInfo == null || !info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
+                info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+                        && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation()))) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+            return Math.max(mLetterboxUiController.getSplitScreenAspectRatio(),
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                    info.getMinAspectRatio());
+        }
+        return info.getMinAspectRatio();
     }
 
     /**
      * Returns true if the activity has maximum or minimum aspect ratio.
      */
     private boolean hasFixedAspectRatio() {
-        return info.hasFixedAspectRatio(getRequestedOrientation());
+        return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 1ed5a85..e0ac37a 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,6 +31,7 @@
 import android.util.SparseArray;
 import android.view.InputApplicationHandle;
 
+import com.android.internal.os.TimeoutRecord;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.criticalevents.CriticalEventLog;
 
@@ -60,7 +61,8 @@
         mService = service;
     }
 
-    void notifyAppUnresponsive(InputApplicationHandle applicationHandle, String reason) {
+    void notifyAppUnresponsive(InputApplicationHandle applicationHandle,
+            TimeoutRecord timeoutRecord) {
         preDumpIfLockTooSlow();
         final ActivityRecord activity;
         synchronized (mService.mGlobalLock) {
@@ -70,31 +72,31 @@
                         + ". Dropping notifyNoFocusedWindowAnr request");
                 return;
             }
-            Slog.i(TAG_WM, "ANR in " + activity.getName() + ".  Reason: " + reason);
-            dumpAnrStateLocked(activity, null /* windowState */, reason);
+            Slog.i(TAG_WM, "ANR in " + activity.getName() + ".  Reason: " + timeoutRecord.mReason);
+            dumpAnrStateLocked(activity, null /* windowState */, timeoutRecord.mReason);
             mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
         }
-        activity.inputDispatchingTimedOut(reason, INVALID_PID);
+        activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
     }
 
 
     /**
      * Notify a window was unresponsive.
      *
-     * @param token - the input token of the window
-     * @param pid - the pid of the window, if known
-     * @param reason - the reason for the window being unresponsive
+     * @param token         - the input token of the window
+     * @param pid           - the pid of the window, if known
+     * @param timeoutRecord - details for the timeout
      */
     void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
-            @NonNull String reason) {
-        if (notifyWindowUnresponsive(token, reason)) {
+            @NonNull TimeoutRecord timeoutRecord) {
+        if (notifyWindowUnresponsive(token, timeoutRecord)) {
             return;
         }
         if (!pid.isPresent()) {
             Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
             return;
         }
-        notifyWindowUnresponsive(pid.getAsInt(), reason);
+        notifyWindowUnresponsive(pid.getAsInt(), timeoutRecord);
     }
 
     /**
@@ -103,7 +105,8 @@
      * @return true if the window was identified by the given input token and the request was
      *         handled, false otherwise.
      */
-    private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
+    private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken,
+            TimeoutRecord timeoutRecord) {
         preDumpIfLockTooSlow();
         final int pid;
         final boolean aboveSystem;
@@ -119,14 +122,14 @@
             // embedded, then we will blame the pid instead.
             activity = (windowState.mInputChannelToken == inputToken)
                     ? windowState.mActivityRecord : null;
-            Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
+            Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason);
             aboveSystem = isWindowAboveSystem(windowState);
-            dumpAnrStateLocked(activity, windowState, reason);
+            dumpAnrStateLocked(activity, windowState, timeoutRecord.mReason);
         }
         if (activity != null) {
-            activity.inputDispatchingTimedOut(reason, pid);
+            activity.inputDispatchingTimedOut(timeoutRecord, pid);
         } else {
-            mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
+            mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, timeoutRecord);
         }
         return true;
     }
@@ -134,15 +137,16 @@
     /**
      * Notify a window owned by the provided pid was unresponsive.
      */
-    private void notifyWindowUnresponsive(int pid, String reason) {
+    private void notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord) {
+        Slog.i(TAG_WM,
+                "ANR in input window owned by pid=" + pid + ". Reason: " + timeoutRecord.mReason);
         synchronized (mService.mGlobalLock) {
-            Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason);
-            dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
+            dumpAnrStateLocked(null /* activity */, null /* windowState */, timeoutRecord.mReason);
         }
 
         // We cannot determine the z-order of the window, so place the anr dialog as high
         // as possible.
-        mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason);
+        mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, timeoutRecord);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 7731f28..acbf1a4 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
 
@@ -26,6 +27,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.media.projection.MediaProjectionManager;
 import android.os.IBinder;
 import android.provider.DeviceConfig;
 import android.view.ContentRecordingSession;
@@ -38,7 +40,7 @@
 /**
  * Manages content recording for a particular {@link DisplayContent}.
  */
-final class ContentRecorder {
+final class ContentRecorder implements WindowContainerListener {
 
     /**
      * The key for accessing the device config that controls if task recording is supported.
@@ -51,6 +53,8 @@
     @NonNull
     private final DisplayContent mDisplayContent;
 
+    @Nullable private final MediaProjectionManagerWrapper mMediaProjectionManager;
+
     /**
      * The session for content recording, or null if this DisplayContent is not being used for
      * recording.
@@ -73,8 +77,26 @@
      */
     @Nullable private Rect mLastRecordedBounds = null;
 
+    /**
+     * The last configuration orientation.
+     */
+    private int mLastOrientation = ORIENTATION_UNDEFINED;
+
     ContentRecorder(@NonNull DisplayContent displayContent) {
+        this(displayContent, () -> {
+            MediaProjectionManager mpm = displayContent.mWmService.mContext.getSystemService(
+                    MediaProjectionManager.class);
+            if (mpm != null) {
+                mpm.stopActiveProjection();
+            }
+        });
+    }
+
+    @VisibleForTesting
+    ContentRecorder(@NonNull DisplayContent displayContent,
+            @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
         mDisplayContent = displayContent;
+        mMediaProjectionManager = mediaProjectionManager;
     }
 
     /**
@@ -99,7 +121,7 @@
     }
 
     /**
-     * Start recording if this DisplayContent no longer has content. Stop recording if it now
+     * Start recording if this DisplayContent no longer has content. Pause recording if it now
      * has content or the display is not on.
      */
     @VisibleForTesting void updateRecording() {
@@ -191,7 +213,7 @@
     /**
      * Stops recording on this DisplayContent, and updates the session details.
      */
-    void remove() {
+    void stopRecording() {
         if (mRecordedSurface != null) {
             // Do not wait for the mirrored surface to be garbage collected, but clean up
             // immediately.
@@ -199,7 +221,20 @@
             mRecordedSurface = null;
             clearContentRecordingSession();
             // Do not need to force remove the VirtualDisplay; this is handled by the media
-            // projection service.
+            // projection service when the display is removed.
+        }
+    }
+
+
+    /**
+     * Ensure recording does not fall back to the display stack; ensure the recording is stopped
+     * and the client notified by tearing down the virtual display.
+     */
+    void stopMediaProjection() {
+        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
+        if (mMediaProjectionManager != null) {
+            mMediaProjectionManager.stopActiveProjection();
         }
     }
 
@@ -329,6 +364,8 @@
                     ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                             "Unable to retrieve task to start recording for "
                                     + "display %d", mDisplayContent.getDisplayId());
+                } else {
+                    taskToRecord.registerWindowContainerListener(this);
                 }
                 return taskToRecord;
             default:
@@ -345,9 +382,9 @@
     /**
      * Exit this recording session.
      * <p>
-     * If this is a task session, tear down the recording entirely. Do not fall back
-     * to recording the entire display on the display stack; this would surprise the user
-     * given they selected task capture.
+     * If this is a task session, stop the recording entirely, including the MediaProjection.
+     * Do not fall back to recording the entire display on the display stack; this would surprise
+     * the user given they selected task capture.
      * </p><p>
      * If this is a display session, just stop recording by layer mirroring. Fall back to recording
      * from the display stack.
@@ -356,26 +393,15 @@
     private void handleStartRecordingFailed() {
         final boolean shouldExitTaskRecording = mContentRecordingSession != null
                 && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+        clearContentRecordingSession();
         if (shouldExitTaskRecording) {
-            // Clean up the cached session first, since tearing down the display will generate
-            // display
-            // events which will trickle back to here.
-            clearContentRecordingSession();
-            tearDownVirtualDisplay();
-        } else {
-            clearContentRecordingSession();
+            // Clean up the cached session first to ensure recording doesn't re-start, since
+            // tearing down the display will generate display events which will trickle back here.
+            stopMediaProjection();
         }
     }
 
     /**
-     * Ensure recording does not fall back to the display stack; ensure the recording is stopped
-     * and the client notified by tearing down the virtual display.
-     */
-    private void tearDownVirtualDisplay() {
-        // TODO(b/219761722) Clean up the VirtualDisplay if task mirroring fails
-    }
-
-    /**
      * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
      * fit and centred in the output surface.
      *
@@ -445,4 +471,37 @@
         }
         return surfaceSize;
     }
+
+    // WindowContainerListener
+    @Override
+    public void onRemoved() {
+        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                "Recorded task is removed, so stop recording on display %d",
+                mDisplayContent.getDisplayId());
+        Task recordedTask = mRecordedWindowContainer.asTask();
+        if (recordedTask == null
+                || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+            return;
+        }
+        recordedTask.unregisterWindowContainerListener(this);
+        // Stop mirroring and teardown.
+        clearContentRecordingSession();
+        // Clean up the cached session first to ensure recording doesn't re-start, since
+        // tearing down the display will generate display events which will trickle back here.
+        stopMediaProjection();
+    }
+
+    // WindowContainerListener
+    @Override
+    public void onMergedOverrideConfigurationChanged(
+            Configuration mergedOverrideConfiguration) {
+        WindowContainerListener.super.onMergedOverrideConfigurationChanged(
+                mergedOverrideConfiguration);
+        onConfigurationChanged(mLastOrientation);
+        mLastOrientation = mergedOverrideConfiguration.orientation;
+    }
+
+    @VisibleForTesting interface MediaProjectionManagerWrapper {
+        void stopActiveProjection();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index fff7637..1efc202 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -56,14 +56,13 @@
      * Updates the current recording session. If a new display is taking over recording, then
      * stops the prior display from recording.
      *
-     * @param incomingSession the new recording session. Should either be {@code null}, to stop
-     *                        the current session, or a session on a new/different display than the
-     *                        current session.
+     * @param incomingSession the new recording session. Should either have a {@code null} token, to
+     *                        stop the current session, or a session on a new/different display
+     *                        than the current session.
      * @param wmService       the window manager service
      */
     void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
             @NonNull WindowManagerService wmService) {
-        // TODO(b/219761722) handle a null session arriving due to task setup failing
         if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
                 || ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
             // Ignore an invalid session, or a session for the same display as currently recording.
@@ -82,8 +81,7 @@
         }
         if (mSession != null) {
             // Update the pre-existing display about the new session.
-            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                    "Pause the recording session on display %s",
+            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Pause the recording session on display %s",
                     mDisplayContent.getDisplayId());
             mDisplayContent.pauseRecording();
             mDisplayContent.setContentRecordingSession(null);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb486d4..fa3fc9f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,7 +53,6 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.GONE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
@@ -950,7 +949,7 @@
 
                 final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
                         .getPreferredModeId(w);
-                if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+                if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
                         && preferredModeId != 0) {
                     mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
                 }
@@ -2710,25 +2709,22 @@
         mCurrentPrivacyIndicatorBounds =
                 mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds);
         if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) {
-            updateDisplayFrames(false /* insetsSourceMayChange */, true /* notifyInsetsChange */);
+            updateDisplayFrames(true /* notifyInsetsChange */);
         }
     }
 
     void onDisplayInfoChanged() {
-        updateDisplayFrames(LOCAL_LAYOUT, LOCAL_LAYOUT);
+        updateDisplayFrames(false /* notifyInsetsChange */);
         mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
     }
 
-    private void updateDisplayFrames(boolean insetsSourceMayChange, boolean notifyInsetsChange) {
+    private void updateDisplayFrames(boolean notifyInsetsChange) {
         if (mDisplayFrames.update(mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
                 calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
-            if (insetsSourceMayChange) {
-                mDisplayPolicy.updateInsetsSourceFramesExceptIme(mDisplayFrames);
-            }
             mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
         }
     }
@@ -4416,13 +4412,20 @@
      */
     @VisibleForTesting
     InsetsControlTarget computeImeControlTarget() {
+        if (mImeInputTarget == null) {
+            // A special case that if there is no IME input target while the IME is being killed,
+            // in case seeing unexpected IME surface visibility change when delivering the IME leash
+            // to the remote insets target during the IME restarting, but the focus window is not in
+            // multi-windowing mode, return null target until the next input target updated.
+            return null;
+        }
+
+        final WindowState imeInputTarget = mImeInputTarget.getWindowState();
         if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
-                || (mImeInputTarget != null
-                        && getImeHostOrFallback(mImeInputTarget.getWindowState())
-                               == mRemoteInsetsControlTarget)) {
+                || getImeHostOrFallback(imeInputTarget) == mRemoteInsetsControlTarget) {
             return mRemoteInsetsControlTarget;
         } else {
-            return mImeInputTarget != null ? mImeInputTarget.getWindowState() : null;
+            return imeInputTarget;
         }
     }
 
@@ -6075,7 +6078,7 @@
         mRemoved = true;
 
         if (mContentRecorder != null) {
-            mContentRecorder.remove();
+            mContentRecorder.stopRecording();
         }
 
         // Only update focus/visibility for the last one because there may be many root tasks are
@@ -6356,6 +6359,15 @@
     }
 
     /**
+     * The MediaProjection instance is torn down.
+     */
+    @VisibleForTesting void stopMediaProjection() {
+        if (mContentRecorder != null) {
+            mContentRecorder.stopMediaProjection();
+        }
+    }
+
+    /**
      * Sets the incoming recording session. Should only be used when starting to record on
      * this display; stopping recording is handled separately when the display is destroyed.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0769406..5221072 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1576,19 +1576,6 @@
         }
     }
 
-    void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
-        sTmpClientFrames.attachedFrame = null;
-        for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
-            final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
-            mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
-                    displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
-                    displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
-                    UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
-                    sTmpClientFrames);
-            win.updateSourceFrame(sTmpClientFrames.frame);
-        }
-    }
-
     void onDisplayInfoChanged(DisplayInfo info) {
         mSystemGestures.onDisplayInfoChanged(info);
     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 33cdd2e..1e9d451 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -36,6 +36,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 
+import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.input.InputManagerService;
 
@@ -95,14 +96,17 @@
      */
     @Override
     public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
-        mService.mAnrController.notifyAppUnresponsive(
-                applicationHandle, "Application does not have a focused window");
+        TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
+                timeoutMessage("Application does not have a focused window"));
+        mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
     }
 
     @Override
     public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
-            @NonNull String reason) {
-        mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
+            String reason) {
+        TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+                timeoutMessage(reason));
+        mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
     }
 
     @Override
@@ -341,6 +345,13 @@
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
 
+    private String timeoutMessage(String reason) {
+        if (reason == null) {
+            return "Input dispatching timed out";
+        }
+        return "Input dispatching timed out (" + reason + ")";
+    }
+
     void dump(PrintWriter pw, String prefix) {
         if (mInputFreezeReason != null) {
             pw.println(prefix + "mInputFreezeReason=" + mInputFreezeReason);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index a618f7c..a0e22e7 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -63,7 +63,7 @@
      * Calculate the default size for a freeform environment. |defaultSize| is used as the default
      * DP size, but if this is null, the portrait phone size is used.
      */
-    static Size getDefaultFreeformSize(@NonNull ActivityInfo info,
+    static Size getDefaultFreeformSize(@NonNull ActivityRecord activityRecord,
             @NonNull TaskDisplayArea displayArea,
             @NonNull ActivityInfo.WindowLayout layout, int orientation,
             @NonNull Rect stableBounds) {
@@ -98,8 +98,8 @@
         final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
 
         // Aspect ratio requirements.
-        final float minAspectRatio = info.getMinAspectRatio(orientation);
-        final float maxAspectRatio = info.getMaxAspectRatio();
+        final float minAspectRatio = activityRecord.getMinAspectRatio();
+        final float maxAspectRatio = activityRecord.info.getMaxAspectRatio();
 
         // Adjust the width and height to the aspect ratio requirements.
         int adjWidth = width;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ec9ee29..c8ed602 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -257,6 +257,10 @@
                             : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
         }
 
+        return getSplitScreenAspectRatio();
+    }
+
+    float getSplitScreenAspectRatio() {
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0128c18..fb68fe6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -242,16 +242,17 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
         int res = mService.relayoutWindow(this, window, attrs,
-                requestedWidth, requestedHeight, viewFlags, flags,
-                outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
+                requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
                 outActiveControls, outSyncSeqIdBundle);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
@@ -260,6 +261,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncIdBundle */);
+    }
+
+    @Override
     public boolean outOfMemory(IWindow window) {
         return mService.outOfMemoryWindow(this, window);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0c10dba..0332935 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5343,7 +5343,7 @@
                     abort = true;
                 }
                 if (abort) {
-                    Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent);
+                    android.util.EventLog.writeEvent(0x534e4554, "238605611", callingUid, "");
                     foundParentInTask = false;
                 } else {
                     parent.deliverNewIntentLocked(callingUid, destIntent, destGrants,
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 7b0337d..1362094 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -746,7 +746,7 @@
 
         // First we get the default size we want.
         displayArea.getStableRect(mTmpStableBounds);
-        final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root.info, displayArea,
+        final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root, displayArea,
                 layout, orientation, mTmpStableBounds);
         mTmpBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
         if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6121ad4..a71c386 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -810,12 +810,17 @@
          */
         public final String imeLayerTargetName;
 
+        /** The surface parent of the IME container. */
+        public final String imeSurfaceParentName;
+
         public ImeTargetInfo(String focusedWindowName, String requestWindowName,
-                String imeControlTargetName, String imeLayerTargetName) {
+                String imeControlTargetName, String imeLayerTargetName,
+                String imeSurfaceParentName) {
             this.focusedWindowName = focusedWindowName;
             this.requestWindowName = requestWindowName;
             this.imeControlTargetName = imeControlTargetName;
             this.imeLayerTargetName = imeLayerTargetName;
+            this.imeSurfaceParentName = imeSurfaceParentName;
         }
     }
 
@@ -882,6 +887,8 @@
      * Must be invoked for a valid MediaProjection session.
      *
      * @param incomingSession the nullable incoming content recording session
+     * @return {@code true} if successfully set the session, or {@code false} if the session
+     * could not be prepared and the session needs to be torn down.
      */
-    public abstract void setContentRecordingSession(ContentRecordingSession incomingSession);
+    public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9c1cc4d..6544f82 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2248,11 +2248,14 @@
     }
 
     public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
-        Arrays.fill(outActiveControls, null);
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncIdBundle) {
+        if (outActiveControls != null) {
+            Arrays.fill(outActiveControls, null);
+        }
         int result = 0;
         boolean configChanged;
         final int pid = Binder.getCallingPid();
@@ -2263,8 +2266,15 @@
             if (win == null) {
                 return 0;
             }
+            if (win.mRelayoutSeq < seq) {
+                win.mRelayoutSeq = seq;
+            } else if (win.mRelayoutSeq > seq) {
+                return 0;
+            }
 
-            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
+                // The client has reported the sync draw, but we haven't finished it yet.
+                // Don't let the client perform a non-sync draw at this time.
                 result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
             }
 
@@ -2427,7 +2437,7 @@
 
             // Create surfaceControl before surface placement otherwise layout will be skipped
             // (because WS.isGoneForLayout() is true when there is no surface.
-            if (shouldRelayout) {
+            if (shouldRelayout && outSurfaceControl != null) {
                 try {
                     result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                 } catch (Exception e) {
@@ -2466,22 +2476,25 @@
                 winAnimator.mEnterAnimationPending = false;
                 winAnimator.mEnteringAnimation = false;
 
-                if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
-                    // We already told the client to go invisible, but the message may not be
-                    // handled yet, or it might want to draw a last frame. If we already have a
-                    // surface, let the client use that, but don't create new surface at this point.
-                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
-                    winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                } else {
-                    if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
-
-                    try {
-                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
-                                + win.mAttrs.getTitle());
-                        outSurfaceControl.release();
-                    } finally {
+                if (outSurfaceControl != null) {
+                    if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+                        // We already told the client to go invisible, but the message may not be
+                        // handled yet, or it might want to draw a last frame. If we already have a
+                        // surface, let the client use that, but don't create new surface at this
+                        // point.
+                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+                        winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    } else {
+                        if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+                        try {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+                                    + win.mAttrs.getTitle());
+                            outSurfaceControl.release();
+                        } finally {
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                        }
                     }
                 }
 
@@ -2534,20 +2547,16 @@
                 win.mResizedWhileGone = false;
             }
 
-            win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
-                    false /* useLatestConfig */, shouldRelayout);
+            if (outFrames != null && outMergedConfiguration != null) {
+                win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
+                        false /* useLatestConfig */, shouldRelayout);
 
-            // Set resize-handled here because the values are sent back to the client.
-            win.onResizeHandled();
+                // Set resize-handled here because the values are sent back to the client.
+                win.onResizeHandled();
+            }
 
-            outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
-            if (DEBUG) {
-                Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
-                        + ", requestedWidth=" + requestedWidth
-                        + ", requestedHeight=" + requestedHeight
-                        + ", viewVisibility=" + viewVisibility
-                        + "\nRelayout returning frame=" + outFrames.frame
-                        + ", surface=" + outSurfaceControl);
+            if (outInsetsState != null) {
+                outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
             }
 
             ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2558,14 +2567,16 @@
             }
             win.mInRelayout = false;
 
-            if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
-                    && (win.mSyncSeqId > win.mLastSeqIdSentToRelayout)) {
-                win.markRedrawForSyncReported();
-
-                win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
-                outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
-            } else {
-                outSyncIdBundle.putInt("seqid", -1);
+            if (outSyncIdBundle != null) {
+                final int maybeSyncSeqId;
+                if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
+                        && win.mSyncSeqId > lastSyncSeqId) {
+                    maybeSyncSeqId = win.mSyncSeqId;
+                    win.markRedrawForSyncReported();
+                } else {
+                    maybeSyncSeqId = -1;
+                }
+                outSyncIdBundle.putInt("seqid", maybeSyncSeqId);
             }
 
             if (configChanged) {
@@ -2574,7 +2585,9 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            getInsetsSourceControls(win, outActiveControls);
+            if (outActiveControls != null) {
+                getInsetsSourceControls(win, outActiveControls);
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -8202,6 +8215,7 @@
             final String requestWindowName;
             final String imeControlTargetName;
             final String imeLayerTargetName;
+            final String imeSurfaceParentName;
             synchronized (mGlobalLock) {
                 final WindowState focusedWin = mWindowMap.get(focusedToken);
                 focusedWindowName = focusedWin != null ? focusedWin.getName() : "null";
@@ -8218,15 +8232,17 @@
                     }
                     final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING);
                     imeLayerTargetName = target != null ? target.getWindow().getName() : "null";
+                    final SurfaceControl imeParent = dc.mInputMethodSurfaceParent;
+                    imeSurfaceParentName = imeParent != null ? imeParent.toString() : "null";
                     if (show) {
                         dc.onShowImeRequested();
                     }
                 } else {
-                    imeControlTargetName = imeLayerTargetName = "no-display";
+                    imeControlTargetName = imeLayerTargetName = imeSurfaceParentName = "no-display";
                 }
             }
             return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName,
-                    imeLayerTargetName);
+                    imeLayerTargetName, imeSurfaceParentName);
         }
 
         @Override
@@ -8291,14 +8307,15 @@
         }
 
         @Override
-        public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
+        public boolean setContentRecordingSession(
+                @Nullable ContentRecordingSession incomingSession) {
             synchronized (mGlobalLock) {
-                // Allow the controller to handle teardown or a non-task session.
+                // Allow the controller to handle teardown of a non-task session.
                 if (incomingSession == null
                         || incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
                     mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
                             WindowManagerService.this);
-                    return;
+                    return true;
                 }
                 // For a task session, find the activity identified by the launch cookie.
                 final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
@@ -8306,15 +8323,14 @@
                 if (wct == null) {
                     Slog.w(TAG, "Handling a new recording session; unable to find the "
                             + "WindowContainerToken");
-                    mContentRecordingController.setContentRecordingSessionLocked(null,
-                            WindowManagerService.this);
-                    return;
+                    return false;
                 }
                 // Replace the launch cookie in the session details with the task's
                 // WindowContainerToken.
                 incomingSession.setTokenToRecord(wct.asBinder());
                 mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
                         WindowManagerService.this);
+                return true;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 283010e..d222a56 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -389,7 +389,6 @@
      * examine the git commit message introducing this comment and variable.2
      */
     int mSyncSeqId = 0;
-    int mLastSeqIdSentToRelayout = 0;
 
     /** The last syncId associated with a prepareSync or 0 when no sync is active. */
     int mPrepareSyncSeqId = 0;
@@ -425,6 +424,7 @@
     boolean mHaveFrame;
     boolean mObscured;
 
+    int mRelayoutSeq = -1;
     int mLayoutSeq = -1;
 
     /**
@@ -1349,29 +1349,15 @@
         final WindowFrames windowFrames = mWindowFrames;
         mTmpRect.set(windowFrames.mParentFrame);
 
-        if (LOCAL_LAYOUT) {
-            windowFrames.mCompatFrame.set(clientWindowFrames.frame);
+        windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
+        windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
+        windowFrames.mFrame.set(clientWindowFrames.frame);
 
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            if (mGlobalScale != 1f) {
-                // The frames sent from the client need to be adjusted to the real coordinate space.
-                windowFrames.mFrame.scale(mGlobalScale);
-                windowFrames.mDisplayFrame.scale(mGlobalScale);
-                windowFrames.mParentFrame.scale(mGlobalScale);
-            }
-        } else {
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-
-            windowFrames.mCompatFrame.set(windowFrames.mFrame);
-            if (mInvGlobalScale != 1f) {
-                // Also, the scaled frame that we report to the app needs to be adjusted to be in
-                // its coordinate space.
-                windowFrames.mCompatFrame.scale(mInvGlobalScale);
-            }
+        windowFrames.mCompatFrame.set(windowFrames.mFrame);
+        if (mInvGlobalScale != 1f) {
+            // Also, the scaled frame that we report to the app needs to be adjusted to be in
+            // its coordinate space.
+            windowFrames.mCompatFrame.scale(mInvGlobalScale);
         }
         windowFrames.setParentFrameWasClippedByDisplayCutout(
                 clientWindowFrames.isParentFrameClippedByDisplayCutout);
@@ -1415,13 +1401,6 @@
 
         updateSourceFrame(windowFrames.mFrame);
 
-        if (LOCAL_LAYOUT) {
-            if (!mHaveFrame) {
-                // The first frame should not be considered as moved.
-                updateLastFrames();
-            }
-        }
-
         if (mActivityRecord != null && !mIsChildWindow) {
             mActivityRecord.layoutLetterbox(this);
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 57a8013..427c3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -100,7 +100,9 @@
         // Capture the listeners.
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+
+        mFlexibilityController =
+                new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class));
         mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
 
         verify(mContext).registerReceiver(receiverCaptor.capture(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 7a70e7a..953a72d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -116,8 +116,8 @@
         LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
 
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
-
-        mFlexibilityController = new FlexibilityController(mService);
+        mFlexibilityController =
+                new FlexibilityController(mService, mock(PrefetchController.class));
 
         // Freeze the clocks at this moment in time
         JobSchedulerService.sSystemClock =
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6d9f48d..1b39add 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -16,17 +16,25 @@
 
 package com.android.server.job.controllers;
 
+import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE;
+import static android.app.job.JobInfo.BIAS_TOP_APP;
 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.job.controllers.FlexibilityController.FcConstants.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -67,12 +75,12 @@
 public class FlexibilityControllerTest {
     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
     private static final int SOURCE_USER_ID = 0;
-
+    private static final long FROZEN_TIME = 100L;
     private MockitoSession mMockingSession;
     private FlexibilityController mFlexibilityController;
     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
     private JobStore mJobStore;
-    private FlexibilityController.FcConstants mFcConstants;
+    private FlexibilityController.FcConfig mFcConfig;
 
     @Mock
     private AlarmManager mAlarmManager;
@@ -80,6 +88,8 @@
     private Context mContext;
     @Mock
     private JobSchedulerService mJobSchedulerService;
+    @Mock
+    private PrefetchController mPrefetchController;
 
     @Before
     public void setup() {
@@ -117,13 +127,15 @@
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
         // Freeze the clocks at a moment in time
         JobSchedulerService.sSystemClock =
-                Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         // Initialize real objects.
-        mFlexibilityController = new FlexibilityController(mJobSchedulerService);
-        mFcConstants = mFlexibilityController.getFcConstants();
+        mFlexibilityController = new FlexibilityController(mJobSchedulerService,
+                mPrefetchController);
+        mFcConfig = mFlexibilityController.getFcConfig();
 
+        setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
     }
 
@@ -138,7 +150,25 @@
         mDeviceConfigPropertiesBuilder.setBoolean(key, val);
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFlexibilityController.onConstantsUpdatedLocked();
+        }
+    }
+
+    private void setDeviceConfigLong(String key, Long val) {
+        mDeviceConfigPropertiesBuilder.setLong(key, val);
+        synchronized (mFlexibilityController.mLock) {
+            mFlexibilityController.prepareForUpdatedConstantsLocked();
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFlexibilityController.onConstantsUpdatedLocked();
+        }
+    }
+
+    private void setDeviceConfigString(String key, String val) {
+        mDeviceConfigPropertiesBuilder.setString(key, val);
+        synchronized (mFlexibilityController.mLock) {
+            mFlexibilityController.prepareForUpdatedConstantsLocked();
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
             mFlexibilityController.onConstantsUpdatedLocked();
         }
     }
@@ -149,76 +179,277 @@
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
         JobInfo jobInfo = job.build();
-        return JobStatus.createFromJobInfo(
+        JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+        js.enqueueTime = FROZEN_TIME;
+        return js;
+    }
+
+    /**
+     * Tests that the there are equally many percents to drop constraints as there are constraints
+     */
+    @Test
+    public void testDefaultVariableValues() {
+        assertEquals(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS,
+                mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
+        );
     }
 
     @Test
-    public void testGetNextConstraintDropTimeElapsed() {
+    public void testOnConstantsUpdated_DefaultFlexibility() {
+        JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
+        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
+        assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_DeadlineProximity() {
+        JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
+        setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
+        mFlexibilityController.mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+        assertEquals(0, js.getNumRequiredFlexibleConstraints());
+    }
+
+    @Test
+    public void testOnConstantsUpdated_FallbackDeadline() {
+        JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
+        assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
+        assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_PercentsToDropConstraints() {
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
+                new int[] {10, 20, 30, 40});
+        assertEquals(110L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        js.adjustNumRequiredFlexibleConstraints(-1);
+        assertEquals(120L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        js.adjustNumRequiredFlexibleConstraints(-1);
+        assertEquals(130L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+        js.enqueueTime = 100L;
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetNextConstraintDropTimeElapsedLocked() {
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
         JobStatus js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        assertEquals(0, js.getEarliestRunTime());
-        assertEquals(1100L, js.getLatestRunTimeElapsed());
-        assertEquals(100L, js.enqueueTime);
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
+        assertEquals(1000 + FROZEN_TIME, js.getLatestRunTimeElapsed());
+        assertEquals(FROZEN_TIME, js.enqueueTime);
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(600L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(700L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(800L, nextTimeToDropNumConstraints);
 
         // delay, no deadline
         jb = createJob(0).setMinimumLatency(800000L);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(130400100, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(156320100L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(182240100L, nextTimeToDropNumConstraints);
 
         // no delay, no deadline
         jb = createJob(0);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(129600100, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(155520100L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(181440100L, nextTimeToDropNumConstraints);
 
         // delay, deadline
         jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(700L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(800L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(900L, nextTimeToDropNumConstraints);
     }
 
     @Test
+    public void testCurPercent() {
+        long deadline = 1000;
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
+        JobStatus js = createJobStatus("time", jb);
+
+        assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        assertEquals(deadline + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME), ZoneOffset.UTC);
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+        long delay = 100;
+        deadline = 1100;
+        jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
+        js = createJobStatus("time", jb);
+
+        assertEquals(FROZEN_TIME + delay,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        assertEquals(deadline + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME + delay), ZoneOffset.UTC);
+
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME + delay), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+    }
+
+    @Test
+    public void testGetLifeCycleBeginningElapsedLocked_prefetch() {
+        // prefetch with lifecycle
+        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
+        assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch with enqueue
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch with delay
+        jb = createJob(0).setPrefetch(true).setMinimumLatency(200);
+        js = createJobStatus("time", jb);
+        assertEquals(200 + FROZEN_TIME, js.getEarliestRunTime());
+        assertEquals(js.getEarliestRunTime(),
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch without estimate
+        mFlexibilityController.mPrefetchLifeCycleStart
+                .add(js.getUserId(), js.getSourcePackageName(), 500L);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetLifeCycleBeginningElapsedLocked_nonPrefetch() {
+        // delay
+        long delay = 100;
+        JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
+        JobStatus js = createJobStatus("time", jb);
+        assertEquals(delay + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // no delay
+        jb = createJob(0);
+        js = createJobStatus("time", jb);
+        assertEquals(FROZEN_TIME,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetLifeCycleEndElapsedLocked_prefetch() {
+        // prefetch no estimate
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        // prefetch with estimate
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
+        assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+    }
+    @Test
+    public void testGetLifeCycleEndElapsedLocked_nonPrefetch() {
+        // deadline
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
+        JobStatus js = createJobStatus("time", jb);
+        assertEquals(1000L + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        // no deadline
+        jb = createJob(0);
+        js = createJobStatus("time", jb);
+        assertEquals(100L + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+    }
+
+    @Test
     public void testWontStopJobFromRunning() {
         JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
         // Stop satisfied constraints from causing a false positive.
@@ -233,8 +464,9 @@
     public void testFlexibilityTracker() {
         FlexibilityController.FlexibilityTracker flexTracker =
                 mFlexibilityController.new
-                        FlexibilityTracker(FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
+                        FlexibilityTracker(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS);
 
+        assertEquals(4, flexTracker.size());
         JobStatus[] jobs = new JobStatus[4];
         JobInfo.Builder jb;
         for (int i = 0; i < jobs.length; i++) {
@@ -282,6 +514,29 @@
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
             assertEquals(0, trackedJobs.get(3).size());
+
+            flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+            assertEquals(0, trackedJobs.get(0).size());
+            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(2, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
+
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2);
+
+            assertEquals(1, trackedJobs.get(0).size());
+            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
+
+            JobSchedulerService.sElapsedRealtimeClock =
+                    Clock.fixed(Instant.ofEpochMilli((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
+                            + HOUR_IN_MILLIS), ZoneOffset.UTC);
+
+            flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+            assertEquals(0, trackedJobs.get(0).size());
+            assertEquals(1, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
         }
     }
 
@@ -303,14 +558,6 @@
     }
 
     @Test
-    public void testExceptions_Prefetch() {
-        JobInfo.Builder jb = createJob(0);
-        jb.setPrefetch(true);
-        JobStatus js = createJobStatus("testExceptions_Prefetch", jb);
-        assertFalse(js.hasFlexibilityConstraint());
-    }
-
-    @Test
     public void testExceptions_NoFlexibleConstraints() {
         JobInfo.Builder jb = createJob(0);
         jb.setRequiresDeviceIdle(true);
@@ -381,7 +628,7 @@
     @Test
     public void testSetConstraintSatisfied_Jobs() {
         JobInfo.Builder jb;
-        int[] constraintPermutations = {
+        int[] constraintCombinations = {
                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
                 CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
@@ -393,9 +640,9 @@
         };
 
         int constraints;
-        for (int i = 0; i < constraintPermutations.length; i++) {
+        for (int i = 0; i < constraintCombinations.length; i++) {
             jb = createJob(i);
-            constraints = constraintPermutations[i];
+            constraints = constraintCombinations[i];
             jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
             jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
             jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
@@ -410,8 +657,8 @@
 
         assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
 
-        for (int i = 0; i < constraintPermutations.length; i++) {
-            constraints = constraintPermutations[i];
+        for (int i = 0; i < constraintCombinations.length; i++) {
+            constraints = constraintCombinations[i];
             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
                     (constraints & CONSTRAINT_CHARGING) != 0);
             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
@@ -427,6 +674,130 @@
         }
     }
 
+    @Test
+    public void testResetJobNumDroppedConstraints() {
+        JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
+        js.adjustNumRequiredFlexibleConstraints(3);
+
+        mFlexibilityController.mFlexibilityTracker.add(js);
+
+        assertEquals(3, js.getNumRequiredFlexibleConstraints());
+        assertEquals(0, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(155L), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
+
+        assertEquals(2, js.getNumRequiredFlexibleConstraints());
+        assertEquals(1, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+        assertEquals(2, js.getNumRequiredFlexibleConstraints());
+        assertEquals(1, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(140L), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+        assertEquals(3, js.getNumRequiredFlexibleConstraints());
+        assertEquals(0, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(175), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+        assertEquals(0, js.getNumRequiredFlexibleConstraints());
+        assertEquals(3, js.getNumDroppedFlexibleConstraints());
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(165L), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+        assertEquals(1, js.getNumRequiredFlexibleConstraints());
+        assertEquals(2, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+    }
+
+    @Test
+    public void testOnPrefetchCacheUpdated() {
+        ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
+        JobInfo.Builder jb = createJob(22).setPrefetch(true);
+        JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
+        jobs.add(js);
+        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
+                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+
+        mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC);
+
+        mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
+                jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
+                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+
+        assertEquals(150L,
+                (long) mFlexibilityController.mPrefetchLifeCycleStart
+                        .get(js.getSourceUserId(), js.getSourcePackageName()));
+        assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        assertEquals(1150L,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+        assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+        assertEquals(650L, mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js));
+        assertEquals(3, js.getNumRequiredFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+    }
+
+    /**
+     * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
+     * the estimated launch time was updated and the last time the app was opened.
+     * When the UID bias updates it means the app might have been opened.
+     * This tests that the cached value is updated properly.
+     */
+    @Test
+    public void testUidUpdatesLifeCycle() {
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("uidTest", jb);
+        mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+        mJobStore.add(js);
+
+        final ArraySet<String> pkgs = new ArraySet<>();
+        pkgs.add(js.getSourcePackageName());
+        when(mJobSchedulerService.getPackagesForUidLocked(js.getUid())).thenReturn(pkgs);
+
+        setUidBias(js.getUid(), BIAS_TOP_APP);
+        setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+        assertEquals(100L, (long) mFlexibilityController.mPrefetchLifeCycleStart
+                .getOrDefault(js.getSourceUserId(), js.getSourcePackageName(), 0L));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(50L), ZoneOffset.UTC);
+
+        setUidBias(js.getUid(), BIAS_TOP_APP);
+        setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+        assertEquals(100L, (long) mFlexibilityController
+                .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
+
+    }
+
     private void setUidBias(int uid, int bias) {
         int prevBias = mJobSchedulerService.getUidBias(uid);
         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index f15e60f..df523fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -33,6 +33,7 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEADLINE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEVICE_NOT_DOZING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_STORAGE_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_TIMING_DELAY;
@@ -790,6 +791,83 @@
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
     }
 
+    @Test
+    public void testWouldBeReadyWithConstraint_FlexibilityDoesNotAffectReadiness() {
+        final JobStatus job = createJobStatus(
+                new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+
+        markImplicitConstraintsSatisfied(job, false);
+        job.setFlexibilityConstraintSatisfied(0, false);
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, true);
+        job.setFlexibilityConstraintSatisfied(0, false);
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, false);
+        job.setFlexibilityConstraintSatisfied(0, true);
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, true);
+        job.setFlexibilityConstraintSatisfied(0, true);
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+    }
+
+    @Test
+    public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
+        final JobStatus job = createJobStatus(
+                new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+        job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
+        markImplicitConstraintsSatisfied(job, true);
+        assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        markImplicitConstraintsSatisfied(job, false);
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), true);
+        markImplicitConstraintsSatisfied(job, true);
+        assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        markImplicitConstraintsSatisfied(job, false);
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+    }
+
     private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
         job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
         job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
@@ -797,7 +875,6 @@
                 sElapsedRealtimeClock.millis(), isSatisfied, false);
         job.setBackgroundNotRestrictedConstraintSatisfied(
                 sElapsedRealtimeClock.millis(), isSatisfied, false);
-        job.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
     }
 
     private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index bcdfc35..7242b1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -480,4 +480,32 @@
 
         sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
     }
+
+    @Test
+    public void testRegisterOnPrefetchChangedListener() {
+        when(mUsageStatsManagerInternal
+                .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
+                .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
+        // Needs to get wrapped in an array to get accessed by an inner class.
+        final boolean[] onPrefetchCacheChangedCalled = new boolean[1];
+        final PrefetchController.PrefetchChangedListener prefetchChangedListener =
+                new PrefetchController.PrefetchChangedListener() {
+                    @Override
+                    public void onPrefetchCacheUpdated(
+                            ArraySet<JobStatus> jobs, int userId, String pkgName,
+                            long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+                        onPrefetchCacheChangedCalled[0] = true;
+                    }
+                };
+        mPrefetchController.registerPrefetchChangedListener(prefetchChangedListener);
+
+        JobStatus jobStatus = createJobStatus("testRegisterOnPrefetchChangedListener", 1);
+        trackJobs(jobStatus);
+
+        mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
+                SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
+        verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
+
+        assertTrue(onPrefetchCacheChangedCalled[0]);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
rename to services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 9e986be..da7664b 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +17,10 @@
 package com.android.server.tare;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.util.ArraySet;
 import android.util.SparseLongArray;
@@ -99,7 +102,9 @@
 
     @Before
     public void setUp() {
-        mEconomicPolicy = new MockEconomicPolicy(mock(InternalResourceService.class));
+        final InternalResourceService irs = mock(InternalResourceService.class);
+        when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+        mEconomicPolicy = new MockEconomicPolicy(irs);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 13510adb..d90d8b8b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -68,7 +68,7 @@
     private MockitoSession mMockingSession;
     private Scribe mScribeUnderTest;
     private File mTestFileDir;
-    private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+    private final List<InstalledPackageInfo> mInstalledPackages = new ArrayList<>();
     private final List<Analyst.Report> mReports = new ArrayList<>();
 
     @Mock
@@ -455,6 +455,6 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(pkgInfo);
+        mInstalledPackages.add(new InstalledPackageInfo(pkgInfo));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index c940ef5..0b84a60 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -37,6 +37,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.os.TimeoutRecord;
 import com.android.server.appop.AppOpsService;
 import com.android.server.wm.WindowProcessController;
 
@@ -111,12 +112,15 @@
         final WindowProcessController parentProcess = mock(WindowProcessController.class);
         final boolean aboveSystem = false;
         final String annotation = "test";
+        final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+                annotation);
         mAnrHelper.appNotResponding(mAnrApp, activityShortComponentName, appInfo,
-                parentShortComponentName, parentProcess, aboveSystem, annotation);
+                parentShortComponentName, parentProcess, aboveSystem, timeoutRecord);
 
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
                 eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
-                eq(parentProcess), eq(aboveSystem), eq(annotation), eq(false) /* onlyDumpSelf */);
+                eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+                eq(false) /* onlyDumpSelf */);
     }
 
     @Test
@@ -129,11 +133,13 @@
             processingLatch.await();
             return null;
         }).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
-                anyBoolean(), anyString(), anyBoolean());
+                anyBoolean(), any(), anyBoolean());
         final ApplicationInfo appInfo = new ApplicationInfo();
+        final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+                "annotation");
         final Runnable reportAnr = () -> mAnrHelper.appNotResponding(mAnrApp,
                 "activityShortComponentName", appInfo, "parentShortComponentName",
-                null /* parentProcess */, false /* aboveSystem */, "annotation");
+                null /* parentProcess */, false /* aboveSystem */, timeoutRecord);
         reportAnr.run();
         // This should be skipped because the pid is pending in queue.
         reportAnr.run();
@@ -149,6 +155,6 @@
         processingLatch.countDown();
         // There is only one ANR reported.
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
-                anyString(), any(), any(), any(), anyBoolean(), anyString(), anyBoolean());
+                anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 6538a17..70519e4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -34,6 +34,7 @@
 
 import androidx.test.filters.FlakyTest;
 
+import com.android.internal.os.TimeoutRecord;
 import com.android.server.LocalServices;
 import com.android.server.wm.ActivityTaskManagerService;
 
@@ -195,8 +196,9 @@
 
     private static void appNotResponding(ProcessErrorStateRecord processErrorState,
             String annotation) {
+        TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
         processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
                 null /* parentShortComponentName */, null /* parentProcess */,
-                false /* aboveSystem */, annotation, false /* onlyDumpSelf */);
+                false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
new file mode 100644
index 0000000..8cd58ab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceServiceRegistryTest {
+
+    private static final int SENSOR_ID_1 = 1;
+    private static final int SENSOR_ID_2 = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private IFaceService mFaceService;
+    @Mock
+    private ServiceProvider mProvider1;
+    @Mock
+    private ServiceProvider mProvider2;
+    @Captor
+    private ArgumentCaptor<Integer> mIdCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mStrengthCaptor;
+
+    private FaceSensorPropertiesInternal mProvider1Props;
+    private FaceSensorPropertiesInternal mProvider2Props;
+    private FaceServiceRegistry mRegistry;
+
+    @Before
+    public void setup() {
+        mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
+                STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FaceSensorProperties.TYPE_RGB,
+                true /* supportsFace Detection */,
+                true /* supportsSelfIllumination */,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
+                STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FaceSensorProperties.TYPE_IR,
+                true /* supportsFace Detection */,
+                true /* supportsSelfIllumination */,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
+        when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+        when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+        when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+        when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+        mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService);
+    }
+
+    @Test
+    public void registersAllProviders() throws Exception {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+        assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+        verify(mBiometricService, times(2)).registerAuthenticator(
+                mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
+        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+        assertThat(mStrengthCaptor.getAllValues())
+                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void getsProviderById() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+        assertThat(mRegistry.getProviderForSensor(500)).isNull();
+    }
+
+    @Test
+    public void getsSingleProvider() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void registersListenerBeforeAllRegistered() {
+        final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FaceSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+
+    @Test
+    public void registersListenerAfterAllRegistered() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FaceSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 5f88c99..0e30782 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -24,38 +26,73 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
 
 @Presubmit
 @SmallTest
 public class BiometricStateCallbackTest {
 
-    private BiometricStateCallback mCallback;
+    private static final int USER_ID = 10;
+    private static final int SENSOR_ID = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback;
 
     @Mock
-    BiometricStateListener mBiometricStateListener;
+    private UserManager mUserManager;
+    @Mock
+    private BiometricStateListener mBiometricStateListener;
+    @Mock
+    private FakeProvider mFakeProvider;
+
+    private SensorPropertiesInternal mFakeProviderProps;
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */, List.of(),
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                false /* resetLockoutRequiresChallenge */);
+        when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps));
+        when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps);
+        when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true);
+        when(mUserManager.getAliveUsers()).thenReturn(
+                List.of(new UserInfo(USER_ID, "name", 0)));
 
-        mCallback = new BiometricStateCallback();
+        mCallback = new BiometricStateCallback<>(mUserManager);
         mCallback.registerBiometricStateListener(mBiometricStateListener);
     }
 
     @Test
+    public void startNotifiesEnrollments() {
+        mCallback.start(List.of(mFakeProvider));
+
+        verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true));
+    }
+
+    @Test
     public void testNoEnrollmentsToEnrollments_callbackNotified() {
         testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
                 true /* expectCallback */, true /* expectedCallbackValue */);
@@ -102,4 +139,6 @@
         verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
                 anyBoolean());
     }
+
+    private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {}
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
new file mode 100644
index 0000000..67d94a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FingerprintServiceRegistryTest {
+
+    private static final int SENSOR_ID_1 = 1;
+    private static final int SENSOR_ID_2 = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private IFingerprintService mFingerprintService;
+    @Mock
+    private ServiceProvider mProvider1;
+    @Mock
+    private ServiceProvider mProvider2;
+    @Captor
+    private ArgumentCaptor<Integer> mIdCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mStrengthCaptor;
+
+    private FingerprintSensorPropertiesInternal mProvider1Props;
+    private FingerprintSensorPropertiesInternal mProvider2Props;
+    private FingerprintServiceRegistry mRegistry;
+
+    @Before
+    public void setup() {
+        mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
+                STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
+                STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
+        when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+        when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+        when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+        when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+        mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService);
+    }
+
+    @Test
+    public void registersAllProviders() throws Exception {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+        assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+        verify(mBiometricService, times(2)).registerAuthenticator(
+                mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
+        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+        assertThat(mStrengthCaptor.getAllValues())
+                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void getsProviderById() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+        assertThat(mRegistry.getProviderForSensor(500)).isNull();
+    }
+
+    @Test
+    public void getsSingleProvider() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void registersListenerBeforeAllRegistered() {
+        final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+
+    @Test
+    public void registersListenerAfterAllRegistered() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index ca3677e..a4048a2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -32,7 +32,8 @@
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
-import android.os.RemoteException;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.testing.TestableContext;
@@ -52,6 +53,8 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @Presubmit
 @SmallTest
@@ -94,9 +97,12 @@
 
         mContext.getTestablePermissions().setPermission(
                 USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+    }
 
+    private void initServiceWith(String... aidlInstances) {
         mService = new FingerprintService(mContext, mBiometricContext,
                 () -> mIBiometricService,
+                () -> aidlInstances,
                 (fqName) -> {
                     if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
                     if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
@@ -105,29 +111,50 @@
     }
 
     @Test
-    public void registerAuthenticators_defaultOnly() throws RemoteException {
-        mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+    public void registerAuthenticators_defaultOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void registerAuthenticators_virtualOnly() throws RemoteException {
+    public void registerAuthenticators_virtualOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException {
-        mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of());
+    public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
+        initServiceWith(NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
+    private void waitForRegistration() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+                new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                    @Override
+                    public void onAllAuthenticatorsRegistered(
+                            List<FingerprintSensorPropertiesInternal> sensors) {
+                        latch.countDown();
+                    }
+                });
+        latch.await(5, TimeUnit.SECONDS);
+    }
+
     private static SensorProps createProps(int id, byte strength, byte type) {
         final SensorProps props = new SensorProps();
         props.commonProps = new CommonProps();
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index e305957..c4435b7 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,16 +39,23 @@
         mBrightnessEvent = new BrightnessEvent(1);
         mBrightnessEvent.setReason(
                 getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
+        mBrightnessEvent.setPhysicalDisplayId("test");
         mBrightnessEvent.setLux(100.0f);
+        mBrightnessEvent.setFastAmbientLux(90.0f);
+        mBrightnessEvent.setSlowAmbientLux(85.0f);
         mBrightnessEvent.setPreThresholdLux(150.0f);
         mBrightnessEvent.setTime(System.currentTimeMillis());
+        mBrightnessEvent.setInitialBrightness(25.0f);
         mBrightnessEvent.setBrightness(0.6f);
         mBrightnessEvent.setRecommendedBrightness(0.6f);
         mBrightnessEvent.setHbmMax(0.62f);
+        mBrightnessEvent.setRbcStrength(-1);
         mBrightnessEvent.setThermalMax(0.65f);
+        mBrightnessEvent.setPowerFactor(0.2f);
         mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
         mBrightnessEvent.setFlags(0);
         mBrightnessEvent.setAdjustmentFlags(0);
+        mBrightnessEvent.setAutomaticBrightnessEnabled(true);
     }
 
     @Test
@@ -63,12 +70,15 @@
     public void testToStringWorksAsExpected() {
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
-                "BrightnessEvent: disp=1, brt=0.6, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150"
-                        + ".0, hbmMax=0.62, hbmMode=off, thrmMax=0.65, flags=, reason=doze [ "
-                        + "low_pwr ]";
+                "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
+                + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
+                + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
+                + " [ low_pwr ], autoBrightness=true";
         assertEquals(actualString, expectedString);
     }
 
+
+
     private BrightnessReason getReason(int reason, int modifier) {
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(reason);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java
new file mode 100644
index 0000000..5e92983
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.IContextHubClientCallback;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ContextHubClientBrokerTest {
+    private static final short HOST_ENDPOINT_ID = 123;
+    private static final String ATTRIBUTE_TAG = "attribute_tag";
+    private static final long NANOAPP_ID = 3210L;
+    private ContextHubClientManager mClientManager;
+    private Context mContext;
+    @Mock private IContextHubWrapper mMockContextHubWrapper;
+    @Mock private ContextHubInfo mMockContextHubInfo;
+    @Mock private IContextHubClientCallback mMockCallback;
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Before
+    public void setUp() throws RemoteException {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mClientManager = new ContextHubClientManager(mContext, mMockContextHubWrapper);
+        when(mMockCallback.asBinder()).thenReturn(new Binder());
+    }
+
+    private ContextHubClientBroker createFromCallback() {
+        ContextHubClientBroker broker =
+                new ContextHubClientBroker(
+                        mContext,
+                        mMockContextHubWrapper,
+                        mClientManager,
+                        mMockContextHubInfo,
+                        HOST_ENDPOINT_ID,
+                        mMockCallback,
+                        ATTRIBUTE_TAG,
+                        new ContextHubTransactionManager(
+                                mMockContextHubWrapper, mClientManager, new NanoAppStateManager()),
+                        mContext.getPackageName());
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+        return broker;
+    }
+
+    private ContextHubClientBroker createFromPendingIntent(PendingIntent pendingIntent) {
+        ContextHubClientBroker broker =
+                new ContextHubClientBroker(
+                        mContext,
+                        mMockContextHubWrapper,
+                        mClientManager,
+                        mMockContextHubInfo,
+                        HOST_ENDPOINT_ID,
+                        pendingIntent,
+                        NANOAPP_ID,
+                        ATTRIBUTE_TAG,
+                        new ContextHubTransactionManager(
+                                mMockContextHubWrapper, mClientManager, new NanoAppStateManager()));
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+        return broker;
+    }
+
+    @Test
+    // TODO(b/241016627): We should have similar tests for other public callbacks too.
+    public void testWakeLock_callback_onNanoAppLoaded() {
+        ContextHubClientBroker broker = createFromCallback();
+
+        broker.onNanoAppLoaded(NANOAPP_ID);
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+        broker.callbackFinished();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_callback_multiple() {
+        ContextHubClientBroker broker = createFromCallback();
+
+        broker.onNanoAppLoaded(NANOAPP_ID);
+        broker.onNanoAppUnloaded(NANOAPP_ID);
+        broker.onHubReset();
+
+        broker.callbackFinished();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+        broker.callbackFinished();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+        broker.callbackFinished();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_callback_binderDied() {
+        ContextHubClientBroker broker = createFromCallback();
+
+        broker.binderDied();
+
+        assertThat(broker.isWakelockUsable()).isFalse();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_pendingIntent() throws InterruptedException {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+        CountDownLatch latch = new CountDownLatch(1);
+        PendingIntent.OnFinished onFinishedCallback =
+                (PendingIntent unusedPendingIntent,
+                        Intent unusedIntent,
+                        int resultCode,
+                        String resultData,
+                        Bundle resultExtras) -> {
+                    // verify that the wakelock is held before calling the OnFinished callback.
+                    assertThat(broker.isWakelockUsable()).isTrue();
+                    assertThat(broker.getWakeLock().isHeld()).isTrue();
+                    broker.onSendFinished(
+                            unusedPendingIntent,
+                            unusedIntent,
+                            resultCode,
+                            resultData,
+                            resultExtras);
+                    latch.countDown();
+                };
+
+        broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_pendingIntent_multipleTimes() throws InterruptedException {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+        CountDownLatch latch = new CountDownLatch(3);
+        PendingIntent.OnFinished onFinishedCallback =
+                (PendingIntent unusedPendingIntent,
+                        Intent unusedIntent,
+                        int resultCode,
+                        String resultData,
+                        Bundle resultExtras) -> {
+                    // verify that the wakelock is held before calling the OnFinished callback.
+                    assertThat(broker.isWakelockUsable()).isTrue();
+                    assertThat(broker.getWakeLock().isHeld()).isTrue();
+                    broker.onSendFinished(
+                            unusedPendingIntent,
+                            unusedIntent,
+                            resultCode,
+                            resultData,
+                            resultExtras);
+                    latch.countDown();
+                };
+
+        broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+        broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+        broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_pendingIntent_binderDied() {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+
+        broker.binderDied();
+
+        // The wakelock should still be usable because a pending intent exists.
+        assertThat(broker.isWakelockUsable()).isTrue();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_close_pendingIntent() {
+        ContextHubClientBroker broker = createFromCallback();
+
+        broker.close();
+
+        // The wakelock should be unusable because broker.close() clears out the pending intent.
+        assertThat(broker.isWakelockUsable()).isFalse();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+
+    @Test
+    public void testWakeLock_onNanoAppUnloaded_closeBeforeCallback() {
+        ContextHubClientBroker broker = createFromCallback();
+
+        broker.onNanoAppUnloaded(NANOAPP_ID);
+        broker.close();
+
+        assertThat(broker.isWakelockUsable()).isFalse();
+        assertThat(broker.getWakeLock().isHeld()).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS b/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS
new file mode 100644
index 0000000..5bdc0a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS
@@ -0,0 +1,2 @@
+# Bug component ID: 156070
+include /services/core/java/com/android/server/location/contexthub/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
new file mode 100644
index 0000000..6035250
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -0,0 +1,40 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.location.contexthub."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.location.contexthub."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Postsubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
index a62569f..8715afd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -329,6 +329,27 @@
                 state.isInstallAllowed());
     }
 
+    public void testAreAllVerificationsComplete_timeoutSuccessWithSufficient() {
+        PackageVerificationState state = new PackageVerificationState(null);
+
+        state.addRequiredVerifierUid(REQUIRED_UID_1);
+        state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+        assertFalse(state.areAllVerificationsComplete());
+        assertFalse(state.isVerificationComplete());
+        assertFalse(state.isInstallAllowed());
+
+        // Required verifier responded, but still waiting for sufficient.
+        state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+        assertFalse(state.isVerificationComplete());
+
+        // Timeout, verification complete and installation allowed.
+        state.setVerifierResponse(REQUIRED_UID_1,
+                PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
+        assertTrue(state.isVerificationComplete());
+        assertTrue(state.isInstallAllowed());
+    }
+
     public void testAreAllVerificationsComplete_onlyVerificationPasses() {
         PackageVerificationState state = new PackageVerificationState(null);
         state.addRequiredVerifierUid(REQUIRED_UID_1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 06b7112..5d48501 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -266,6 +266,8 @@
         }
 
         assertThat(createUser("User beyond", userType, 0)).isNull();
+
+        assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isTrue();
     }
 
     @MediumTest
@@ -467,8 +469,10 @@
     @MediumTest
     @Test
     public void testThereCanBeOnlyOneGuest() throws Exception {
+        assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isTrue();
         UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
         assertThat(userInfo1).isNotNull();
+        assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isFalse();
         UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST);
         assertThat(userInfo2).isNull();
     }
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 7746bd6..0635cc4 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AlertDialog;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.fingerprint.FingerprintManager;
@@ -36,10 +35,13 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
+import android.testing.TestableResources;
 import android.view.Window;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.R;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -47,7 +49,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 import java.util.List;
 
@@ -68,17 +69,22 @@
                     BiometricStateListener.STATE_BP_AUTH,
                     BiometricStateListener.STATE_AUTH_OTHER);
 
+    private static final Integer AUTO_DISMISS_DIALOG = 500;
+
     @Rule
     public TestableContext mContext =
             new TestableContext(InstrumentationRegistry.getContext(), null);
 
-    @Mock private PackageManager mPackageManager;
-    @Mock private FingerprintManager mFingerprintManager;
-    @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
-    @Mock private AlertDialog mAlertDialog;
-    @Mock private Window mWindow;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private SideFpsToast mDialog;
+    @Mock
+    private Window mWindow;
 
-    private TestLooper mLooper = new TestLooper();
+    private final TestLooper mLooper = new TestLooper();
     private SideFpsEventHandler mEventHandler;
     private BiometricStateListener mBiometricStateListener;
 
@@ -88,16 +94,17 @@
 
         mContext.addMockSystemService(PackageManager.class, mPackageManager);
         mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+        TestableResources resources = mContext.getOrCreateTestableResources();
+        resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG);
 
-        when(mDialogBuilder.create()).thenReturn(mAlertDialog);
-        when(mAlertDialog.getWindow()).thenReturn(mWindow);
+        when(mDialog.getWindow()).thenReturn(mWindow);
 
         mEventHandler =
                 new SideFpsEventHandler(
                         mContext,
                         new Handler(mLooper.getLooper()),
                         mContext.getSystemService(PowerManager.class),
-                        () -> mDialogBuilder);
+                        (ctx) -> mDialog);
     }
 
     @Test
@@ -108,7 +115,7 @@
         assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog, never()).show();
+        verify(mDialog, never()).show();
     }
 
     @Test
@@ -120,7 +127,7 @@
             assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
 
             mLooper.dispatchAll();
-            verify(mAlertDialog, never()).show();
+            verify(mDialog, never()).show();
         }
     }
 
@@ -133,7 +140,7 @@
             assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
 
             mLooper.dispatchAll();
-            verify(mAlertDialog, never()).show();
+            verify(mDialog, never()).show();
         }
     }
 
@@ -148,7 +155,7 @@
         assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog, never()).show();
+        verify(mDialog, never()).show();
     }
 
     @Test
@@ -159,18 +166,18 @@
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog, never()).show();
+        verify(mDialog, never()).show();
     }
 
     @Test
-    public void promptsWhenBPisActive() throws Exception {
+    public void doesNotpromptWhenBPisActive() throws Exception {
         setupWithSensor(true /* hasSideFps */, true /* initialized */);
 
         setBiometricState(BiometricStateListener.STATE_BP_AUTH);
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog, never()).show();
+        verify(mDialog, never()).show();
     }
 
     @Test
@@ -181,57 +188,40 @@
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog).show();
-        verify(mAlertDialog, never()).dismiss();
+        verify(mDialog).show();
     }
 
     @Test
-    public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
+    public void dialogDismissesAfterTime() throws Exception {
         setupWithSensor(true /* hasSfps */, true /* initialized */);
 
         setBiometricState(BiometricStateListener.STATE_ENROLLING);
-        when(mAlertDialog.isShowing()).thenReturn(true);
+        when(mDialog.isShowing()).thenReturn(true);
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog).show();
-
-        mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-        mLooper.moveTimeForward(10000);
+        verify(mDialog).show();
+        mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
         mLooper.dispatchAll();
-
-        verify(mAlertDialog).dismiss();
+        verify(mDialog).dismiss();
     }
 
     @Test
-    public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
+    public void dialogDoesNotDismissOnSensorTouch() throws Exception {
         setupWithSensor(true /* hasSfps */, true /* initialized */);
 
         setBiometricState(BiometricStateListener.STATE_ENROLLING);
-        when(mAlertDialog.isShowing()).thenReturn(true);
+        when(mDialog.isShowing()).thenReturn(true);
         assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
 
         mLooper.dispatchAll();
-        verify(mAlertDialog).show();
+        verify(mDialog).show();
 
         mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-        assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
+        mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
         mLooper.dispatchAll();
-        verify(mAlertDialog, never()).dismiss();
-    }
 
-    @Test
-    public void showDialogAfterTap() throws Exception {
-        setupWithSensor(true /* hasSfps */, true /* initialized */);
-
-        setBiometricState(BiometricStateListener.STATE_ENROLLING);
-        when(mAlertDialog.isShowing()).thenReturn(true);
-        mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-        assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
-        mLooper.dispatchAll();
-        verify(mAlertDialog).show();
+        verify(mDialog, never()).dismiss();
     }
 
     private void setBiometricState(@BiometricStateListener.State int newState) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 5a4ce5da..3a6c0eb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -20,10 +20,8 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -75,9 +73,6 @@
 
     @Test
     public void testScheduleJob() {
-        // if asked, the job doesn't currently exist yet
-        when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
-
         final int rescheduleTimeMillis = 350;  // arbitrary number
 
         // attempt to schedule the job
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 69ba8ad..e2c94c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -32,9 +32,11 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.VirtualDisplay;
@@ -54,6 +56,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.CountDownLatch;
 
@@ -69,17 +73,23 @@
 public class ContentRecorderTests extends WindowTestsBase {
     private static final IBinder TEST_TOKEN = new RecordingTestToken();
     private static IBinder sTaskWindowContainerToken;
+    private Task mTask;
     private final ContentRecordingSession mDisplaySession =
             ContentRecordingSession.createDisplaySession(TEST_TOKEN);
     private ContentRecordingSession mTaskSession;
     private static Point sSurfaceSize;
     private ContentRecorder mContentRecorder;
+    @Mock private ContentRecorder.MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
     private SurfaceControl mRecordedSurface;
     // Handle feature flag.
     private ConfigListener mConfigListener;
     private CountDownLatch mLatch;
 
+    private VirtualDisplay mVirtualDisplay;
+
     @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         // GIVEN SurfaceControl can successfully mirror the provided surface.
         sSurfaceSize = new Point(
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
@@ -89,13 +99,14 @@
         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
 
         // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
-        VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
+        mVirtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
                 sSurfaceSize.x, sSurfaceSize.y,
                 DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
-        final int displayId = virtualDisplay.getDisplay().getDisplayId();
+        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
         mWm.mRoot.onDisplayAdded(displayId);
         final DisplayContent virtualDisplayContent = mWm.mRoot.getDisplayContent(displayId);
-        mContentRecorder = new ContentRecorder(virtualDisplayContent);
+        mContentRecorder = new ContentRecorder(virtualDisplayContent,
+                mMediaProjectionManagerWrapper);
         spyOn(virtualDisplayContent);
 
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -119,6 +130,8 @@
     @After
     public void teardown() {
         DeviceConfig.removeOnPropertiesChangedListener(mConfigListener);
+        mVirtualDisplay.release();
+        mWm.mRoot.onDisplayRemoved(mVirtualDisplay.getDisplay().getDisplayId());
     }
 
     @Test
@@ -182,7 +195,7 @@
         mContentRecorder.setContentRecordingSession(session);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
-        // TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
     }
 
     @Test
@@ -193,7 +206,7 @@
         mContentRecorder.setContentRecordingSession(invalidTaskSession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
-        // TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
     }
 
     @Test
@@ -221,9 +234,24 @@
         mContentRecorder.updateRecording();
         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
 
-        verify(mTransaction, atLeastOnce()).setPosition(eq(mRecordedSurface), anyFloat(),
+        verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
                 anyFloat());
-        verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+        verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testOnTaskConfigurationChanged_resizesSurface() {
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+
+        Configuration config = mTask.getConfiguration();
+        config.orientation = ORIENTATION_PORTRAIT;
+        mTask.onConfigurationChanged(config);
+
+        verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+                anyFloat());
+        verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
                 anyFloat(), anyFloat());
     }
 
@@ -243,21 +271,31 @@
     }
 
     @Test
-    public void testRemove_stopsRecording() {
+    public void testStopRecording_stopsRecording() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
-        mContentRecorder.remove();
+        mContentRecorder.stopRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
-    public void testRemove_neverRecording() {
-        mContentRecorder.remove();
+    public void testStopRecording_neverRecording() {
+        mContentRecorder.stopRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
+    public void testRemoveTask_stopsRecording() {
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+
+        mTask.removeImmediately();
+
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
+    }
+
+    @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
@@ -300,10 +338,10 @@
      */
     private IBinder setUpTaskWindowContainerToken(DisplayContent displayContent) {
         final Task rootTask = createTask(displayContent);
-        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+        mTask = createTaskInRootTask(rootTask, 0 /* userId */);
         // Ensure the task is not empty.
-        createActivityRecord(displayContent, task);
-        return task.getTaskInfo().token.asBinder();
+        createActivityRecord(displayContent, mTask);
+        return mTask.getTaskInfo().token.asBinder();
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 7698033d..342d68b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -87,7 +87,7 @@
     @Test
     public void testSetContentRecordingSessionLocked_invalidToken_notAccepted() {
         ContentRecordingController controller = new ContentRecordingController();
-        // GIVEN an invalid display session (null token).
+        // GIVEN a session with a null token.
         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(null);
         session.setDisplayId(DEFAULT_DISPLAY);
         // WHEN updating the session.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2cf9c01..28d2aa1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1252,7 +1252,15 @@
     public void testComputeImeControlTarget() throws Exception {
         final DisplayContent dc = createNewDisplay();
         dc.setRemoteInsetsController(createDisplayWindowInsetsController());
-        dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
+        dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app");
+
+        // Expect returning null IME control target when the focus window has not yet been the
+        // IME input target (e.g. IME is restarting) in fullscreen windowing mode.
+        dc.setImeInputTarget(null);
+        assertFalse(dc.mCurrentFocus.inMultiWindowMode());
+        assertNull(dc.computeImeControlTarget());
+
+        dc.setImeInputTarget(dc.mCurrentFocus);
         dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState());
         assertEquals(dc.getImeInputTarget().getWindowState(), dc.computeImeControlTarget());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fde6e3c..6d33aaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -97,6 +97,7 @@
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -1537,6 +1538,108 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
     public void testSplitAspectRatioForUnresizableLandscapeApps() {
         // Set up a display in portrait and ignoring orientation request.
         int screenWidth = 1400;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1e0e836..a131084 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
+import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
@@ -58,6 +60,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.attention.AttentionManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -186,6 +189,12 @@
     final int mUser;
     final Context mContext;
 
+    @Nullable final AttentionManagerInternal mAttentionManagerInternal;
+
+    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
+            this::setProximityMeters;
+
+
     volatile HotwordDetectionServiceIdentity mIdentity;
     private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
     private Instant mLastRestartInstant;
@@ -206,6 +215,8 @@
     private @NonNull ServiceConnection mRemoteHotwordDetectionService;
     private IBinder mAudioFlinger;
     private boolean mDebugHotwordLogging = false;
+    @GuardedBy("mLock")
+    private double mProximityMeters = PROXIMITY_UNKNOWN;
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -233,6 +244,10 @@
         mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
 
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+        mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+        }
 
         mLastRestartInstant = Instant.now();
         updateStateAfterProcessStart(options, sharedMemory);
@@ -397,6 +412,9 @@
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         }
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
+        }
     }
 
     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
@@ -464,6 +482,7 @@
                         mSoftwareCallback.onError();
                         return;
                     }
+                    saveProximityMetersToBundle(result);
                     mSoftwareCallback.onDetected(result, null, null);
                     if (result != null) {
                         Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -591,6 +610,7 @@
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
+                    saveProximityMetersToBundle(result);
                     externalCallback.onKeyphraseDetected(recognitionEvent, result);
                     if (result != null) {
                         Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -1159,6 +1179,20 @@
         });
     }
 
+    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+        synchronized (mLock) {
+            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
+                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+            }
+        }
+    }
+
+    private void setProximityMeters(double proximityMeters) {
+        synchronized (mLock) {
+            mProximityMeters = proximityMeters;
+        }
+    }
+
     private static void bestEffortClose(Closeable... closeables) {
         for (Closeable closeable : closeables) {
             bestEffortClose(closeable);
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 2048964..bcba2fe 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -46,7 +46,6 @@
 android_test {
     name: "dex-builder-test",
     srcs: [
-        "src/android/startop/test/ApkLayoutCompilerTest.java",
         "src/android/startop/test/DexBuilderTest.java",
         "src/android/startop/test/LayoutCompilerTest.java",
         "src/android/startop/test/TestClass.java",
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
deleted file mode 100644
index 230e8df..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
+++ /dev/null
@@ -1,57 +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 android.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import android.view.View;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class ApkLayoutCompilerTest {
-    static ClassLoader loadDexFile() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
-        return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
-                ClassLoader.getSystemClassLoader());
-    }
-
-    @BeforeClass
-    public static void setup() throws Exception {
-        // ensure PackageManager has compiled the layouts.
-        Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
-        pm.waitFor();
-    }
-
-    @Test
-    public void loadAndInflateLayout1() throws Exception {
-        ClassLoader dex_file = loadDexFile();
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout1.invoke(null, context, R.layout.layout1);
-    }
-
-    @Test
-    public void loadAndInflateLayout2() throws Exception {
-        ClassLoader dex_file = loadDexFile();
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout2.invoke(null, context, R.layout.layout2);
-    }
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d18dfe..5ad4edf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,6 +39,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
 import android.telephony.gba.TlsParams;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsReasonInfo;
@@ -1125,6 +1126,27 @@
     public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
 
     /**
+     * The data call retry configuration for different types of APN.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
+            "carrier_data_call_retry_config_strings";
+
+    /**
+     * Delay in milliseconds between trying APN from the pool
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
+            "carrier_data_call_apn_delay_default_long";
+
+    /**
+     * Faster delay in milliseconds between trying APN from the pool
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
+            "carrier_data_call_apn_delay_faster_long";
+
+    /**
      * Delay in milliseconds for retrying APN after disconnect
      * @hide
      */
@@ -1132,6 +1154,21 @@
             "carrier_data_call_apn_retry_after_disconnect_long";
 
     /**
+     * The maximum times for telephony to retry data setup on the same APN requested by
+     * network through the data setup response retry timer
+     * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
+     * asking device to retry data setup forever and causes power consumption issue. For infinite
+     * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
+     *
+     * Note if network does not suggest any retry timer, frameworks uses the retry configuration
+     * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
+     * be configured there.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
+            "carrier_data_call_retry_network_requested_max_count_int";
+
+    /**
      * Data call setup permanent failure causes by the carrier
      */
     public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1151,6 +1188,19 @@
             "carrier_metered_roaming_apn_types_strings";
 
     /**
+     * APN types that are not allowed on cellular
+     * @hide
+     */
+    public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wwan_disallowed_apn_types_string_array";
+
+    /**
+     * APN types that are not allowed on IWLAN
+     * @hide
+     */
+    public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wlan_disallowed_apn_types_string_array";
+    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -8369,6 +8419,7 @@
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
      * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
      *
+     * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
      * @hide
      */
     public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8770,13 +8821,27 @@
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
+        sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
+                "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+                "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+                "ims:max_retries=10, 5000, 5000, 5000",
+                "others:max_retries=3, 5000, 5000, 5000"});
+        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
+        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
         sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
+        sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
+        sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
+        sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1f97d86..f28408e1a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -194,13 +194,24 @@
         }
 
         @Override
-        public T recompute(Void aVoid) {
+        public T recompute(Void query) {
+            // This always throws on any error.  The exceptions must be handled outside
+            // the cache.
+            try {
+                return mInterfaceMethod.applyOrThrow(TelephonyManager.getSubscriptionService());
+            } catch (Exception re) {
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
+        public T query(Void query) {
             T result = mDefaultValue;
 
             try {
                 ISub iSub = TelephonyManager.getSubscriptionService();
                 if (iSub != null) {
-                    result = mInterfaceMethod.applyOrThrow(iSub);
+                    result = super.query(query);
                 }
             } catch (Exception ex) {
                 Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty);
@@ -229,12 +240,24 @@
 
         @Override
         public T recompute(Integer query) {
+            // This always throws on any error.  The exceptions must be handled outside
+            // the cache.
+            try {
+                return mInterfaceMethod.applyOrThrow(
+                    TelephonyManager.getSubscriptionService(), query);
+            } catch (Exception re) {
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
+        public T query(Integer query) {
             T result = mDefaultValue;
 
             try {
                 ISub iSub = TelephonyManager.getSubscriptionService();
                 if (iSub != null) {
-                    result = mInterfaceMethod.applyOrThrow(iSub, query);
+                    result = super.query(query);
                 }
             } catch (Exception ex) {
                 Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty);
@@ -4118,4 +4141,3 @@
                         usageSetting, subscriptionId, mContext.getOpPackageName()));
     }
 }
-
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7893992..2453f3c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -140,6 +140,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -234,6 +235,54 @@
     public static final int NETWORK_SELECTION_MODE_AUTO = 1;
     public static final int NETWORK_SELECTION_MODE_MANUAL = 2;
 
+    /**
+     * Reasons for Radio being powered off.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RADIO_POWER_REASON_"},
+            value = {
+                    RADIO_POWER_REASON_USER,
+                    RADIO_POWER_REASON_THERMAL,
+                    RADIO_POWER_REASON_CARRIER,
+                    RADIO_POWER_REASON_NEARBY_DEVICE})
+    public @interface RadioPowerReason {}
+
+    /**
+     * This reason is used when users want to turn off radio, e.g., users turn on airplane mode.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_USER = 0;
+    /**
+     * This reason is used when radio needs to be turned off due to thermal.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_THERMAL = 1;
+    /**
+     * This reason is used when carriers want to turn off radio. A privileged app can request to
+     * turn off radio via the system service
+     * {@link com.android.carrierdefaultapp.CaptivePortalLoginActivity}, which subsequently calls
+     * the system APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_CARRIER = 2;
+    /**
+     * Used to reduce power on a battery-constrained device when Telephony services are available
+     * via a paired device which is nearby.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
+
     /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
     /** @hide */
     static public final int OTASP_UNINITIALIZED = 0;
@@ -10506,34 +10555,155 @@
         }
     }
 
-    /** @hide */
+    /**
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean setRadio(boolean turnOn) {
+        boolean result = true;
         try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setRadio(turnOn);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#setRadio", e);
+            if (turnOn) {
+                clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            } else {
+                requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            }
+        } catch (Exception e) {
+            String calledFunction =
+                    turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+            Log.e(TAG, "Error calling " + calledFunction, e);
+            result = false;
         }
-        return false;
+        return result;
     }
 
-    /** @hide */
+    /**
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean setRadioPower(boolean turnOn) {
+        boolean result = true;
+        try {
+            if (turnOn) {
+                clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            } else {
+                requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            }
+        } catch (Exception e) {
+            String calledFunction =
+                    turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+            Log.e(TAG, "Error calling " + calledFunction, e);
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+     * no reason to power it off. When any of the voters want to power it off, it will be turned
+     * off. In case of emergency, the radio will be turned on even if there are some reasons for
+     * powering it off, and these radio off votes will be cleared.
+     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+     * check its vote.
+     *
+     * @param reason The reason for powering off radio.
+     * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public void requestRadioPowerOffForReason(@RadioPowerReason int reason) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setRadioPower(turnOn);
+            if (telephony != null) {
+                if (!telephony.requestRadioPowerOffForReason(getSubId(), reason)) {
+                    throw new IllegalStateException("Telephony service is not available.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#setRadioPower", e);
+            Log.e(TAG, "Error calling ITelephony#requestRadioPowerOffForReason", e);
+            e.rethrowAsRuntimeException();
         }
-        return false;
+    }
+
+    /**
+     * Remove the vote on powering off the radio for a reason, as requested by
+     * {@link requestRadioPowerOffForReason}.
+     *
+     * @param reason The reason for powering off radio.
+     * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public void clearRadioPowerOffForReason(@RadioPowerReason int reason) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (!telephony.clearRadioPowerOffForReason(getSubId(), reason)) {
+                    throw new IllegalStateException("Telephony service is not available.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#clearRadioPowerOffForReason", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+     * If the reason set is empty, the radio is on in all cases.
+     *
+     * @return Set of reasons for powering off radio.
+     * @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    @NonNull
+    public Set<Integer> getRadioPowerOffReasons() {
+        Set<Integer> result = new HashSet<>();
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                result.addAll(telephony.getRadioPowerOffReasons(getSubId(),
+                        mContext.getOpPackageName(), mContext.getAttributionTag()));
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getRadioPowerOffReasons", e);
+            e.rethrowAsRuntimeException();
+        }
+        return result;
     }
 
     /**
@@ -13037,20 +13207,21 @@
      *
      * @param enabled control enable or disable radio.
      * @see #resetAllCarrierActions()
+     *
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public void setRadioEnabled(boolean enabled) {
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                service.carrierActionSetRadioEnabled(
-                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
+        if (enabled) {
+            clearRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
+        } else {
+            requestRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
         }
     }
 
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 3ed87e1..f794a79 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -24,7 +24,6 @@
 import android.database.Cursor;
 import android.hardware.radio.V1_5.ApnTypes;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Telephony;
@@ -964,7 +963,7 @@
                 ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
         }
         int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
-        if (mtuV4 == -1) {
+        if (mtuV4 == UNSET_MTU) {
             mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
         }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index da1ffcd..42cac66 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -244,6 +244,44 @@
     boolean setRadioPower(boolean turnOn);
 
     /**
+     * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+     * no reason to power it off. When any of the voters want to power it off, it will be turned
+     * off. In case of emergency, the radio will be turned on even if there are some reasons for
+     * powering it off, and these radio off votes will be cleared.
+     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+     * check its vote.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    boolean requestRadioPowerOffForReason(int subId, int reason);
+
+    /**
+     * Remove the vote on powering off the radio for a reasonas, requested by
+     * {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    boolean clearRadioPowerOffForReason(int subId, int reason);
+
+    /**
+     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param callingPackage The package making the call.
+     * @param callingFeatureId The feature in the package.
+     * @return List of reasons for powering off radio.
+     */
+    List getRadioPowerOffReasons(int subId, String callingPackage, String callingFeatureId);
+
+    /**
      * This method has been removed due to security and stability issues.
      */
     @UnsupportedAppUsage
@@ -2510,6 +2548,9 @@
     CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
             String callingFeatureId);
 
+    /** Check if telephony new data stack is enabled. */
+    boolean isUsingNewDataStack();
+
     /**
      *  @return true if the modem service is set successfully, false otherwise.
      */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index e138d33..be7fb73 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -38,7 +38,10 @@
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+            WindowManagerStateHelper(
+                instrumentation,
+                clearCacheAfterParsing = false
+            ).currentState.wmState.isTablet
         )
         tapl.setExpectedRotationCheckEnabled(true)
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 75900df..d08cb55 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -26,4 +26,9 @@
  * @param rotation New device rotation
  */
 fun Flicker.setRotation(rotation: Int) =
-    ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper)
+    ChangeDisplayOrientationRule.setRotation(
+        rotation,
+        instrumentation,
+        clearCacheAfterParsing = false,
+        wmHelper = wmHelper
+)
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index c4cb33d..4426551 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -123,16 +123,27 @@
         }
 
         boolean found = false;
+        boolean remountSystem = false;
+        boolean remountVendor = false;
         for (String file : files) {
             CommandResult result = getDevice().executeShellV2Command("ls " + file);
             if (result.getStatus() == CommandStatus.SUCCESS) {
                 found = true;
-                break;
+                if (file.startsWith("/system")) {
+                    remountSystem = true;
+                } else if (file.startsWith("/vendor")) {
+                    remountVendor = true;
+                }
             }
         }
 
         if (found) {
-            getDevice().remountSystemWritable();
+            if (remountSystem) {
+                getDevice().remountSystemWritable();
+            }
+            if (remountVendor) {
+                getDevice().remountVendorWritable();
+            }
             for (String file : files) {
                 getDevice().executeShellCommand("rm -rf " + file);
             }
@@ -150,7 +161,11 @@
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
-        getDevice().remountSystemWritable();
+        if ("system".equals(partition)) {
+            getDevice().remountSystemWritable();
+        } else if ("vendor".equals(partition)) {
+            getDevice().remountVendorWritable();
+        }
         assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName));
     }
 
@@ -158,7 +173,7 @@
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
-        getDevice().remountSystemWritable();
+        getDevice().remountVendorWritable();
         File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
         try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
             final String fmt =
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 1664746..a2842b6 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -22,6 +22,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.os.Bundle;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Display;
@@ -30,9 +31,9 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.os.Trace;
 import android.view.Window;
 import android.view.WindowManager;
+
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
 
@@ -280,6 +281,17 @@
         WindowManager.LayoutParams params = w.getAttributes();
 
         int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
+        while (modeIndex != mCurrentModeIndex) {
+            // skip modes with different resolutions
+            Mode currentMode = mDisplayModes[mCurrentModeIndex];
+            Mode nextMode = mDisplayModes[modeIndex];
+            if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
+                    && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+                break;
+            }
+            modeIndex = (modeIndex + 1) % mDisplayModes.length;
+        }
+
         params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId();
         w.setAttributes(params);
 
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index fecc7b3..d02fd83 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1028,7 +1028,6 @@
             // These permissions are required by services implementing services
             // the system binds to (IME, Accessibility, PrintServices, etc.)
             bool hasBindDeviceAdminPermission = false;
-            bool hasBindInputMethodPermission = false;
             bool hasBindAccessibilityServicePermission = false;
             bool hasBindPrintServicePermission = false;
             bool hasBindNfcServicePermission = false;
@@ -1757,7 +1756,6 @@
                     hasMetaHostPaymentCategory = false;
                     hasMetaOffHostPaymentCategory = false;
                     hasBindDeviceAdminPermission = false;
-                    hasBindInputMethodPermission = false;
                     hasBindAccessibilityServicePermission = false;
                     hasBindPrintServicePermission = false;
                     hasBindNfcServicePermission = false;
@@ -1871,9 +1869,7 @@
                             String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR,
                                     &error);
                             if (error == "") {
-                                if (permission == "android.permission.BIND_INPUT_METHOD") {
-                                    hasBindInputMethodPermission = true;
-                                } else if (permission ==
+                                if (permission ==
                                         "android.permission.BIND_ACCESSIBILITY_SERVICE") {
                                     hasBindAccessibilityServicePermission = true;
                                 } else if (permission ==
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index b9de11b..47750fc 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2970,14 +2970,6 @@
                     }
                     e->setNameIndex(keyStrings.add(e->getName(), true));
 
-                    // If this entry has no values for other configs,
-                    // and is the default config, then it is special.  Otherwise
-                    // we want to add it with the config info.
-                    ConfigDescription* valueConfig = NULL;
-                    if (N != 1 || config == nullConfig) {
-                        valueConfig = &config;
-                    }
-
                     status_t err = e->prepareFlatten(&valueStrings, this,
                             &configTypeName, &config);
                     if (err != NO_ERROR) {
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index d19f4cc..12f3a16 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -19,6 +19,8 @@
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.ManualPermissionCheckDetector
 import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
 
@@ -36,6 +38,7 @@
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
         EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
         EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+        ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS
     )
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
new file mode 100644
index 0000000..82eb8ed
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.google.android.lint.model.Method
+
+const val CLASS_STUB = "Stub"
+const val CLASS_CONTEXT = "android.content.Context"
+const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService"
+const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal"
+
+// Enforce permission APIs
+val ENFORCE_PERMISSION_METHODS = listOf(
+        Method(CLASS_CONTEXT, "checkPermission"),
+        Method(CLASS_CONTEXT, "checkCallingPermission"),
+        Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
+        Method(CLASS_CONTEXT, "enforcePermission"),
+        Method(CLASS_CONTEXT, "enforceCallingPermission"),
+        Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
index 192dba1..48540b1d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
@@ -30,13 +30,13 @@
 import com.android.tools.lint.detector.api.interprocedural.searchForPaths
 import com.intellij.psi.PsiAnonymousClass
 import com.intellij.psi.PsiMethod
+import java.util.LinkedList
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UParameter
 import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.visitor.AbstractUastVisitor
-import java.util.LinkedList
 
 /**
  * A lint checker to detect potential package visibility issues for system's APIs. APIs working
@@ -362,14 +362,18 @@
             name: String,
             matchArgument: Boolean = true,
             checkCaller: Boolean = false
-        ): this(clazz, name) {
+        ) : this(clazz, name) {
             this.matchArgument = matchArgument
             this.checkCaller = checkCaller
         }
 
         constructor(
             method: PsiMethod
-        ): this(method.containingClass?.qualifiedName ?: "", method.name)
+        ) : this(method.containingClass?.qualifiedName ?: "", method.name)
+
+        constructor(
+            method: com.google.android.lint.model.Method
+        ) : this(method.clazz, method.name)
     }
 
     /**
@@ -380,7 +384,7 @@
         val typeName: String,
         val parameterName: String
     ) {
-        constructor(uParameter: UParameter): this(
+        constructor(uParameter: UParameter) : this(
             uParameter.type.canonicalText,
             uParameter.name.lowercase()
         )
@@ -405,19 +409,13 @@
         // A valid call path list needs to contain a start node and a sink node
         private const val VALID_CALL_PATH_NODES_SIZE = 2
 
-        private const val CLASS_STUB = "Stub"
         private const val CLASS_STRING = "java.lang.String"
         private const val CLASS_PACKAGE_MANAGER = "android.content.pm.PackageManager"
         private const val CLASS_IPACKAGE_MANAGER = "android.content.pm.IPackageManager"
         private const val CLASS_APPOPS_MANAGER = "android.app.AppOpsManager"
-        private const val CLASS_CONTEXT = "android.content.Context"
         private const val CLASS_BINDER = "android.os.Binder"
         private const val CLASS_PACKAGE_MANAGER_INTERNAL =
             "android.content.pm.PackageManagerInternal"
-        private const val CLASS_ACTIVITY_MANAGER_SERVICE =
-            "com.android.server.am.ActivityManagerService"
-        private const val CLASS_ACTIVITY_MANAGER_INTERNAL =
-            "android.app.ActivityManagerInternal"
 
         // Patterns of package name parameter
         private val PACKAGE_NAME_PATTERNS = setOf(
@@ -455,16 +453,9 @@
         )
 
         // Enforce permission APIs
-        private val ENFORCE_PERMISSION_METHODS = listOf(
-            Method(CLASS_CONTEXT, "checkPermission"),
-            Method(CLASS_CONTEXT, "checkCallingPermission"),
-            Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
-            Method(CLASS_CONTEXT, "enforcePermission"),
-            Method(CLASS_CONTEXT, "enforceCallingPermission"),
-            Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
-            Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
-            Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
-        )
+        private val ENFORCE_PERMISSION_METHODS =
+                com.google.android.lint.ENFORCE_PERMISSION_METHODS
+                        .map(PackageVisibilityDetector::Method)
 
         private val BYPASS_STUBS = listOf(
             "android.content.pm.IPackageDataObserver.Stub",
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
new file mode 100644
index 0000000..8ee3763
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+const val ANNOTATION_ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
+const val ANNOTATION_REQUIRES_NO_PERMISSION = "android.annotation.RequiresNoPermission"
+const val ANNOTATION_PERMISSION_MANUALLY_ENFORCED = "android.annotation.PermissionManuallyEnforced"
+
+val AIDL_PERMISSION_ANNOTATIONS = listOf(
+        ANNOTATION_ENFORCE_PERMISSION,
+        ANNOTATION_REQUIRES_NO_PERMISSION,
+        ANNOTATION_PERMISSION_MANUALLY_ENFORCED
+)
+
+const val IINTERFACE_INTERFACE = "android.os.IInterface"
+
+/**
+ * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
+ * annotation cannot be used.  At time of writing, the mechanism
+ * is not implemented for non java backends.
+ * TODO: b/242564874 (have lint know which interfaces have the c++ backend enabled)
+ * rather than hard coding this list?
+ */
+val EXCLUDED_CPP_INTERFACES = listOf(
+        "AdbTransportType",
+        "FingerprintAndPairDevice",
+        "IAdbCallback",
+        "IAdbManager",
+        "PairDevice",
+        "IStatsBootstrapAtomService",
+        "StatsBootstrapAtom",
+        "StatsBootstrapAtomValue",
+        "FixedSizeArrayExample",
+        "PlaybackTrackMetadata",
+        "RecordTrackMetadata",
+        "SinkMetadata",
+        "SourceMetadata",
+        "IUpdateEngineStable",
+        "IUpdateEngineStableCallback",
+        "AudioCapabilities",
+        "ConfidenceLevel",
+        "ModelParameter",
+        "ModelParameterRange",
+        "Phrase",
+        "PhraseRecognitionEvent",
+        "PhraseRecognitionExtra",
+        "PhraseSoundModel",
+        "Properties",
+        "RecognitionConfig",
+        "RecognitionEvent",
+        "RecognitionMode",
+        "RecognitionStatus",
+        "SoundModel",
+        "SoundModelType",
+        "Status",
+        "IThermalService",
+        "IPowerManager",
+        "ITunerResourceManager"
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
similarity index 96%
rename from tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
rename to tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 9f21618..a415217 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.google.android.lint
+package com.google.android.lint.aidl
 
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.AnnotationInfo
 import com.android.tools.lint.detector.api.AnnotationOrigin
 import com.android.tools.lint.detector.api.AnnotationUsageInfo
 import com.android.tools.lint.detector.api.AnnotationUsageType
-import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
 import com.android.tools.lint.detector.api.Issue
@@ -34,8 +34,8 @@
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 
 /**
@@ -54,12 +54,11 @@
  */
 class EnforcePermissionDetector : Detector(), SourceCodeScanner {
 
-    val ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
     val BINDER_CLASS = "android.os.Binder"
     val JAVA_OBJECT = "java.lang.Object"
 
     override fun applicableAnnotations(): List<String> {
-        return listOf(ENFORCE_PERMISSION)
+        return listOf(ANNOTATION_ENFORCE_PERMISSION)
     }
 
     override fun getApplicableUastTypes(): List<Class<out UElement>> {
@@ -99,8 +98,8 @@
         overriddenMethod: PsiMethod,
         checkEquivalence: Boolean = true
     ) {
-        val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION)
-        val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)
+        val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val location = context.getLocation(element)
         val overridingClass = overridingMethod.parent as PsiClass
         val overriddenClass = overriddenMethod.parent as PsiClass
@@ -133,8 +132,8 @@
         extendedClass: PsiClass,
         checkEquivalence: Boolean = true
     ) {
-        val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION)
-        val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)
+        val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
 
         val location = context.getLocation(element)
         val newClassName = newClass.qualifiedName
@@ -180,7 +179,7 @@
     override fun createUastHandler(context: JavaContext): UElementHandler {
         return object : UElementHandler() {
             override fun visitAnnotation(node: UAnnotation) {
-                if (node.qualifiedName != ENFORCE_PERMISSION) {
+                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
                     return
                 }
                 val method = node.uastParent as? UMethod
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
new file mode 100644
index 0000000..5106111
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiVariable
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.asRecursiveLogString
+
+/**
+ * Helper ADT class that facilitates the creation of lint auto fixes
+ *
+ * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
+ * that should be migrated to @EnforcePermission(allOf={...})
+ *
+ * TODO: handle anyOf style annotations
+ */
+sealed class EnforcePermissionFix {
+    abstract fun locations(): List<Location>
+    abstract fun javaAnnotationParameter(): String
+
+    fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
+
+    companion object {
+        fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
+            SingleFix(
+                getPermissionCheckLocation(context, callExpression),
+                getPermissionCheckArgumentValue(callExpression)
+            )
+
+        fun maybeAddManifestPrefix(permissionName: String): String =
+            if (permissionName.contains(".")) permissionName
+            else "android.Manifest.permission.$permissionName"
+
+        /**
+         * Given a permission check, get its proper location
+         * so that a lint fix can remove the entire expression
+         */
+        private fun getPermissionCheckLocation(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ):
+                Location {
+            val javaPsi = callExpression.javaPsi!!
+            return Location.create(
+                context.file,
+                javaPsi.containingFile?.text,
+                javaPsi.textRange.startOffset,
+                // unfortunately the element doesn't include the ending semicolon
+                javaPsi.textRange.endOffset + 1
+            )
+        }
+
+        /**
+         * Given a permission check and an argument,
+         * pull out the permission value that is being used
+         */
+        private fun getPermissionCheckArgumentValue(
+            callExpression: UCallExpression,
+            argumentPosition: Int = 0
+        ): String {
+
+            val identifier = when (
+                val argument = callExpression.valueArguments.getOrNull(argumentPosition)
+            ) {
+                is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
+                    is USimpleNameReferenceExpression ->
+                        ((selector.resolve() as PsiVariable).computeConstantValue() as String)
+
+                    else -> throw RuntimeException(
+                        "Couldn't resolve argument: ${selector.asRecursiveLogString()}"
+                    )
+                }
+
+                is USimpleNameReferenceExpression -> (
+                        (argument.resolve() as PsiVariable).computeConstantValue() as String)
+
+                is ULiteralExpression -> argument.value as String
+
+                else -> throw RuntimeException(
+                    "Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
+                )
+            }
+
+            return identifier.substringAfterLast(".")
+        }
+    }
+}
+
+data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
+    override fun locations(): List<Location> = listOf(this.location)
+    override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
+}
+data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
+    override fun locations(): List<Location> = this.checks.map { it.location }
+    override fun javaAnnotationParameter(): String =
+        "allOf={${
+            this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
+        }}"
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
new file mode 100644
index 0000000..2cea394
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.CLASS_STUB
+import com.google.android.lint.ENFORCE_PERMISSION_METHODS
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+        listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            val interfaceName = getContainingAidlInterface(node)
+                .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+            val body = (node.uastBody as? UBlockExpression) ?: return
+            val fix = accumulateSimplePermissionCheckFixes(body) ?: return
+
+            val javaRemoveFixes = fix.locations().map {
+                fix()
+                    .replace()
+                    .reformat(true)
+                    .range(it)
+                    .with("")
+                    .autoFix()
+                    .build()
+            }
+
+            val javaAnnotateFix = fix()
+                .annotate(fix.javaAnnotation())
+                .range(context.getLocation(node))
+                .autoFix()
+                .build()
+
+            val message =
+                "$interfaceName permission check can be converted to @EnforcePermission annotation"
+
+            context.report(
+                ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+                fix.locations().last(),
+                message,
+                fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
+            )
+        }
+
+        /**
+         * Walk the expressions in the method, looking for simple permission checks.
+         *
+         * If a single permission check is found at the beginning of the method,
+         * this should be migrated to @EnforcePermission(value).
+         *
+         * If multiple consecutive permission checks are found,
+         * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+         *
+         * As soon as something other than a permission check is encountered, stop looking,
+         * as some other business logic is happening that prevents an automated fix.
+         */
+        private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
+                EnforcePermissionFix? {
+            val singleFixes = mutableListOf<SingleFix>()
+            for (expression in methodBody.expressions) {
+                singleFixes.add(getPermissionCheckFix(expression) ?: break)
+            }
+            return when (singleFixes.size) {
+                0 -> null
+                1 -> singleFixes[0]
+                else -> AllOfFix(singleFixes)
+            }
+        }
+
+        /**
+         * If an expression boils down to a permission check, return
+         * the helper for creating a lint auto fix to @EnforcePermission
+         */
+        private fun getPermissionCheckFix(startingExpression: UElement?):
+                SingleFix? {
+            return when (startingExpression) {
+                is UQualifiedReferenceExpression -> getPermissionCheckFix(
+                    startingExpression.selector
+                )
+
+                is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
+
+                is UCallExpression -> {
+                    return if (isPermissionCheck(startingExpression))
+                        EnforcePermissionFix.fromCallExpression(startingExpression, context)
+                    else null
+                }
+
+                else -> null
+            }
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+            annotation to declare the permissions to be enforced.  The verification code is then
+            generated by the AIDL compiler, which also takes care of annotating the generated java
+            code.
+
+            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+            It also enables easier auditing and review.
+
+            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
+            id = "UseEnforcePermissionAnnotation",
+            briefDescription = "Manual permission check can be @EnforcePermission annotation",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                ManualPermissionCheckDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+        )
+
+        private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
+            val method = callExpression.resolve() ?: return false
+            val className = method.containingClass?.qualifiedName
+            return ENFORCE_PERMISSION_METHODS.any {
+                it.clazz == className && it.name == method.name
+            }
+        }
+
+        /**
+         * given a UMethod, determine if this method is
+         * an entrypoint to an interface generated by AIDL,
+         * returning the interface name if so
+         */
+        fun getContainingAidlInterface(node: UMethod): String? {
+            if (!isInClassCalledStub(node)) return null
+            for (superMethod in node.findSuperMethods()) {
+                for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+                    ?: continue) {
+                    if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+                        return superMethod.containingClass?.name
+                    }
+                }
+            }
+            return null
+        }
+
+        private fun isInClassCalledStub(node: UMethod): Boolean {
+            (node.containingClass as? PsiAnonymousClass)?.let {
+                return it.baseClassReference.referenceName == CLASS_STUB
+            }
+            return node.containingClass?.extendsList?.referenceElements?.any {
+                it.referenceName == CLASS_STUB
+            } ?: false
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
index 7c9df10..3939b61 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.google.android.lint.model
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+/**
+ * Data class to represent a Method
+ */
+data class Method(val clazz: String, val name: String) {
+    override fun toString(): String {
+        return "$clazz#$name"
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
similarity index 99%
rename from tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
rename to tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 2cfc3fb..3c1d1e8 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.android.lint
+package com.google.android.lint.aidl
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFile
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
new file mode 100644
index 0000000..1a1c6bc
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ManualPermissionCheckDetector()
+    override fun getIssues(): List<Issue> = listOf(
+        ManualPermissionCheckDetector
+            .ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testClass() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                        mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -5 +5
+                +     @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+                @@ -7 +8
+                -         mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testAnonClass() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.READ_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testAllOf() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.READ_CONTACTS", "foo");
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.WRITE_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6                                                                                                                                                                                                       
+                +         @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.READ_CONTACTS", "foo");
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.WRITE_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testPrecedingExpressions() {
+        lint().files(
+            java(
+                """
+                    import android.os.Binder;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private mContext Context;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            long uid = Binder.getCallingUid();
+                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    companion object {
+        private val aidlStub: TestFile = java(
+            """
+               package android.test;
+               public interface ITest extends android.os.IInterface {
+                    public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+                    public void test() throws android.os.RemoteException;
+               }
+            """
+        ).indented()
+
+        private val contextStub: TestFile = java(
+            """
+                package android.content;
+                public class Context {
+                    public void enforceCallingOrSelfPermission(String permission, String message) {}
+                }
+            """
+        ).indented()
+
+        private val binderStub: TestFile = java(
+            """
+                package android.os;
+                public class Binder {
+                    public static int getCallingUid() {}
+                }
+            """
+        ).indented()
+
+        val stubs = arrayOf(aidlStub, contextStub, binderStub)
+    }
+}