Merge "HDMI: Do not change Active Source status on all <Routing Change> messages" into main
diff --git a/ADPF_OWNERS b/ADPF_OWNERS
new file mode 100644
index 0000000..e6ca8f4
--- /dev/null
+++ b/ADPF_OWNERS
@@ -0,0 +1,3 @@
+lpy@google.com
+mattbuckley@google.com
+xwxw@google.com
diff --git a/OWNERS b/OWNERS
index 4860acc..8ee488d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -31,9 +31,6 @@
 per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
 
 per-file **.bp,**.mk = hansson@google.com
-per-file *.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
-per-file Android.mk = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
-per-file framework-jarjar-rules.txt = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
 per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
 
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index e805ab9..abb0fa7 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -42,22 +42,6 @@
     }
 
     @Test
-    public void testCreateRenderNodeNoName() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            RenderNode.create(null, null);
-        }
-    }
-
-    @Test
-    public void testCreateRenderNode() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            RenderNode.create("LinearLayout", null);
-        }
-    }
-
-    @Test
     public void testIsValid() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         RenderNode node = RenderNode.create("LinearLayout", null);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 3b5f11b..29afb27 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -40,10 +40,26 @@
  * <p>This service executes each incoming job on a {@link android.os.Handler} running on your
  * application's main thread. This means that you <b>must</b> offload your execution logic to
  * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
- * in blocking any future callbacks from the JobManager - specifically
+ * in blocking any future callbacks from the JobScheduler - specifically
  * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
  * scheduling requirements are no longer being met.</p>
  *
+ * <p class="note">
+ * Since the introduction of JobScheduler, if an app did not return from
+ * {@link #onStartJob(JobParameters)} within several seconds, JobScheduler would consider the app
+ * unresponsive and clean up job execution. In such cases, the app was no longer considered
+ * to be running a job and therefore did not have any of the job lifecycle guarantees outlined
+ * in {@link JobScheduler}. However, prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the failure and cleanup were silent
+ * and apps had no indication that they no longer had job lifecycle guarantees.
+ * Starting with Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * JobScheduler will explicitly trigger an ANR in such cases so that apps and developers
+ * can be aware of the issue.
+ * Similar behavior applies to the return time from {@link #onStopJob(JobParameters)} as well.
+ * <br /> <br />
+ * If you see ANRs, then the app may be doing too much work on the UI thread. Ensure that
+ * potentially long operations are moved to a worker thread.
+ *
  * <p>As a subclass of {@link Service}, there will only be one active instance of any JobService
  * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
  * job IDs but using the same JobService class, that JobService may receive multiple calls to
@@ -240,7 +256,7 @@
      * @param params The parameters identifying this job, similar to what was supplied to the job in
      *               the {@link #onStartJob(JobParameters)} callback, but with the stop reason
      *               included.
-     * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+     * @return {@code true} to indicate to the JobScheduler whether you'd like to reschedule
      * this job based on the retry criteria provided at job creation-time; or {@code false}
      * to end the job entirely (or, for a periodic job, to reschedule it according to its
      * requested periodic criteria). Regardless of the value returned, your job must stop executing.
diff --git a/api/api.go b/api/api.go
index c568a45..a003aba 100644
--- a/api/api.go
+++ b/api/api.go
@@ -110,6 +110,7 @@
 	Api_surface         *string
 	Api_contributions   []string
 	Defaults_visibility []string
+	Previous_api        *string
 }
 
 type Bazel_module struct {
@@ -359,6 +360,7 @@
 		props.Api_contributions = transformArray(
 			modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
 		props.Defaults_visibility = []string{"//visibility:public"}
+		props.Previous_api = proptools.StringPtr(":android.api.public.latest")
 		ctx.CreateModule(java.DefaultsFactory, &props)
 	}
 }
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index bdec8b9..82df555 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -128,7 +128,9 @@
 that time will be dropped. If you are controlling `uinput` by sending commands through standard
 input from an app, you need to wait for [`onInputDeviceAdded`][onInputDeviceAdded] to be called on
 an `InputDeviceListener` before issuing commands to the device. If you are passing a file to
-`uinput`, add a `delay` after the `register` command to let registration complete.
+`uinput`, add a `delay` after the `register` command to let registration complete. You can add a
+`sync` in certain positions, like at the end of the file to get a response when all commands have
+finished processing.
 
 [onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
 
@@ -187,6 +189,38 @@
 }
 ```
 
+### `sync`
+
+A command used to get a response once the command is processed. When several `inject` and `delay`
+commands are used in a row, the `sync` command can be used to track the progress of the command
+queue.
+
+|    Field    |  Type   | Description                                  |
+|:-----------:|:-------:|:---------------------------------------------|
+|    `id`     | integer | Device ID                                    |
+|  `command`  | string  | Must be set to "sync"                        |
+| `syncToken` | string  | The token used to identify this sync command |
+
+Example:
+
+```json5
+{
+  "id": 1,
+  "command": "syncToken",
+  "syncToken": "finished_injecting_events"
+}
+```
+
+This command will result in the following response when it is processed:
+
+```json5
+{
+  "id": 1,
+  "result": "sync",
+  "syncToken": "finished_injecting_events"
+}
+```
+
 ## Notes
 
 The `getevent` utility can used to print out the key events for debugging purposes.
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 6458eef..ad5e70f 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -45,6 +45,7 @@
     private static final int MSG_OPEN_UINPUT_DEVICE = 1;
     private static final int MSG_CLOSE_UINPUT_DEVICE = 2;
     private static final int MSG_INJECT_EVENT = 3;
+    private static final int MSG_SYNC_EVENT = 4;
 
     private final int mId;
     private final HandlerThread mThread;
@@ -122,6 +123,16 @@
     }
 
     /**
+     * Synchronize the uinput command queue by writing a sync response with the provided syncToken
+     * to the output stream when this event is processed.
+     *
+     * @param syncToken  The token for this sync command.
+     */
+    public void syncEvent(String syncToken) {
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
+    }
+
+    /**
      * Close an uinput device.
      *
      */
@@ -174,6 +185,9 @@
                         mCond.notify();
                     }
                     break;
+                case MSG_SYNC_EVENT:
+                    handleSyncEvent((String) msg.obj);
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown device message");
             }
@@ -187,6 +201,18 @@
             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
             mBarrierToken = 0;
         }
+
+        private void handleSyncEvent(String syncToken) {
+            final JSONObject json = new JSONObject();
+            try {
+                json.put("reason", "sync");
+                json.put("id", mId);
+                json.put("syncToken", syncToken);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not create JSON object ", e);
+            }
+            writeOutputObject(json);
+        }
     }
 
     private class DeviceCallback {
@@ -214,7 +240,7 @@
         }
 
         public void onDeviceVibrating(int value) {
-            JSONObject json = new JSONObject();
+            final JSONObject json = new JSONObject();
             try {
                 json.put("reason", "vibrating");
                 json.put("id", mId);
@@ -222,12 +248,7 @@
             } catch (JSONException e) {
                 throw new RuntimeException("Could not create JSON object ", e);
             }
-            try {
-                mOutputStream.write(json.toString().getBytes());
-                mOutputStream.flush();
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+            writeOutputObject(json);
         }
 
         public void onDeviceError() {
@@ -238,6 +259,15 @@
         }
     }
 
+    private void writeOutputObject(JSONObject json) {
+        try {
+            mOutputStream.write(json.toString().getBytes());
+            mOutputStream.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     static int getEvdevEventTypeByLabel(String label) {
         final var type = nativeGetEvdevEventTypeByLabel(label);
         if (type < 0) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index cddb407..9d8f1f4 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -39,9 +39,19 @@
 public class Event {
     private static final String TAG = "UinputEvent";
 
-    public static final String COMMAND_REGISTER = "register";
-    public static final String COMMAND_DELAY = "delay";
-    public static final String COMMAND_INJECT = "inject";
+    enum Command {
+        REGISTER("register"),
+        DELAY("delay"),
+        INJECT("inject"),
+        SYNC("sync");
+
+        final String mCommandName;
+
+        Command(String command) {
+            mCommandName = command;
+        }
+    }
+
     private static final int EV_KEY = 0x01;
     private static final int EV_REL = 0x02;
     private static final int EV_ABS = 0x03;
@@ -87,7 +97,7 @@
     }
 
     private int mId;
-    private String mCommand;
+    private Command mCommand;
     private String mName;
     private int mVid;
     private int mPid;
@@ -98,12 +108,13 @@
     private int mFfEffectsMax = 0;
     private String mInputport;
     private SparseArray<InputAbsInfo> mAbsInfo;
+    private String mSyncToken;
 
     public int getId() {
         return mId;
     }
 
-    public String getCommand() {
+    public Command getCommand() {
         return mCommand;
     }
 
@@ -147,6 +158,10 @@
         return mInputport;
     }
 
+    public String getSyncToken() {
+        return mSyncToken;
+    }
+
     /**
      * Convert an event to String.
      */
@@ -177,7 +192,14 @@
         }
 
         private void setCommand(String command) {
-            mEvent.mCommand = command;
+            Objects.requireNonNull(command, "Command must not be null");
+            for (Command cmd : Command.values()) {
+                if (cmd.mCommandName.equals(command)) {
+                    mEvent.mCommand = cmd;
+                    return;
+                }
+            }
+            throw new IllegalStateException("Unrecognized command: " + command);
         }
 
         public void setName(String name) {
@@ -220,27 +242,38 @@
             mEvent.mInputport = port;
         }
 
+        public void setSyncToken(String syncToken) {
+            mEvent.mSyncToken = Objects.requireNonNull(syncToken, "Sync token must not be null");
+        }
+
         public Event build() {
             if (mEvent.mId == -1) {
                 throw new IllegalStateException("No event id");
             } else if (mEvent.mCommand == null) {
                 throw new IllegalStateException("Event does not contain a command");
             }
-            if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
-                if (mEvent.mConfiguration == null) {
-                    throw new IllegalStateException(
-                            "Device registration is missing configuration");
+            switch (mEvent.mCommand) {
+                case REGISTER -> {
+                    if (mEvent.mConfiguration == null) {
+                        throw new IllegalStateException(
+                                "Device registration is missing configuration");
+                    }
                 }
-            } else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
-                if (mEvent.mDuration <= 0) {
-                    throw new IllegalStateException("Delay has missing or invalid duration");
+                case DELAY -> {
+                    if (mEvent.mDuration <= 0) {
+                        throw new IllegalStateException("Delay has missing or invalid duration");
+                    }
                 }
-            } else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
-                if (mEvent.mInjections  == null) {
-                    throw new IllegalStateException("Inject command is missing injection data");
+                case INJECT -> {
+                    if (mEvent.mInjections == null) {
+                        throw new IllegalStateException("Inject command is missing injection data");
+                    }
                 }
-            } else {
-                throw new IllegalStateException("Unknown command " + mEvent.mCommand);
+                case SYNC -> {
+                    if (mEvent.mSyncToken == null) {
+                        throw new IllegalStateException("Sync command is missing sync token");
+                    }
+                }
             }
             return mEvent;
         }
@@ -307,6 +340,9 @@
                             case "port":
                                 eb.setInputport(mReader.nextString());
                                 break;
+                            case "syncToken":
+                                eb.setSyncToken(mReader.nextString());
+                                break;
                             default:
                                 mReader.skipValue();
                         }
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 740578e..47b7a354 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -25,6 +25,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
+import java.util.Objects;
 
 /**
  * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream
@@ -96,28 +97,27 @@
 
     private void process(Event e) {
         final int index = mDevices.indexOfKey(e.getId());
-        if (index >= 0) {
-            Device d = mDevices.valueAt(index);
-            if (Event.COMMAND_DELAY.equals(e.getCommand())) {
-                d.addDelay(e.getDuration());
-            } else if (Event.COMMAND_INJECT.equals(e.getCommand())) {
-                d.injectEvent(e.getInjections());
-            } else {
-                if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
-                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
-                } else {
-                    error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
-                }
+        if (index < 0) {
+            if (e.getCommand() != Event.Command.REGISTER) {
+                Log.e(TAG, "Unknown device id specified. Ignoring event.");
+                return;
             }
-        } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
             registerDevice(e);
-        } else {
-            Log.e(TAG, "Unknown device id specified. Ignoring event.");
+            return;
+        }
+
+        final Device d = mDevices.valueAt(index);
+        switch (Objects.requireNonNull(e.getCommand())) {
+            case REGISTER ->
+                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
+            case INJECT -> d.injectEvent(e.getInjections());
+            case DELAY -> d.addDelay(e.getDuration());
+            case SYNC -> d.syncEvent(e.getSyncToken());
         }
     }
 
     private void registerDevice(Event e) {
-        if (!Event.COMMAND_REGISTER.equals(e.getCommand())) {
+        if (!Event.Command.REGISTER.equals(e.getCommand())) {
             throw new IllegalStateException(
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index f40cbb6..363e9d4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9410,8 +9410,8 @@
     method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method public boolean isRequestPinAppWidgetSupported();
-    method public void notifyAppWidgetViewDataChanged(int[], int);
-    method public void notifyAppWidgetViewDataChanged(int, int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
@@ -12830,7 +12830,7 @@
     field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
     field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
-    field public static final String FEATURE_THREADNETWORK = "android.hardware.threadnetwork";
+    field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -14351,11 +14351,13 @@
     method public void execSQL(String, Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+    method public long getLastChangedRowCount();
     method public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
+    method public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(String, String, android.content.ContentValues);
@@ -33666,6 +33668,7 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
+    method public void setPreferPowerEfficiency(boolean);
     method public void setThreads(@NonNull int[]);
     method public void updateTargetWorkDuration(long);
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eca2015..d480315 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6276,11 +6276,13 @@
   }
 
   public final class GnssMeasurementRequest implements android.os.Parcelable {
+    method @NonNull public android.os.WorkSource getWorkSource();
     method public boolean isCorrelationVectorOutputsEnabled();
   }
 
   public static final class GnssMeasurementRequest.Builder {
     method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
   }
 
   public final class GnssReflectingPlane implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 76d4386..3429c7c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3285,6 +3285,9 @@
     field @Deprecated protected int mCapabilities;
   }
 
+  public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+  }
+
 }
 
 package android.text {
@@ -3876,6 +3879,9 @@
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
 
+  public final class InsertModeGesture extends android.view.inputmethod.CancellableHandwritingGesture implements android.os.Parcelable {
+  }
+
 }
 
 package android.view.inspector {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a09d7dc..cc716ec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -34,7 +34,6 @@
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
-import static android.window.ConfigurationHelper.shouldUpdateWindowMetricsBounds;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -6163,11 +6162,6 @@
     public static boolean shouldReportChange(@Nullable Configuration currentConfig,
             @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
             int handledConfigChanges, boolean alwaysReportChange) {
-        // Always report changes in window configuration bounds
-        if (shouldUpdateWindowMetricsBounds(currentConfig, newConfig)) {
-            return true;
-        }
-
         final int publicDiff = currentConfig.diffPublicOnly(newConfig);
         // Don't report the change if there's no public diff between current and new config.
         if (publicDiff == 0) {
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1ebf565..a87187b 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -55,6 +55,11 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 final class SharedPreferencesImpl implements SharedPreferences {
     private static final String TAG = "SharedPreferencesImpl";
@@ -119,6 +124,10 @@
     private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
     private int mNumSync = 0;
 
+    private static final ThreadPoolExecutor sLoadExecutor = new ThreadPoolExecutor(0, 1, 10L,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+            new SharedPreferencesThreadFactory());
+
     @UnsupportedAppUsage
     SharedPreferencesImpl(File file, int mode) {
         mFile = file;
@@ -135,11 +144,10 @@
         synchronized (mLock) {
             mLoaded = false;
         }
-        new Thread("SharedPreferencesImpl-load") {
-            public void run() {
-                loadFromDisk();
-            }
-        }.start();
+
+        sLoadExecutor.execute(() -> {
+            loadFromDisk();
+        });
     }
 
     private void loadFromDisk() {
@@ -874,4 +882,14 @@
         }
         mcr.setDiskWriteResult(false, false);
     }
+
+
+    private static final class SharedPreferencesThreadFactory implements ThreadFactory {
+        @Override
+        public Thread newThread(Runnable runnable) {
+            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+            thread.setName("SharedPreferences");
+            return thread;
+        }
+    }
 }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index b0180c1..b0edc3d 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1206,12 +1206,14 @@
             return null;
         }
 
-        final ScreenshotHardwareBuffer screenshotBuffer =
-                syncScreenCapture.getBuffer();
+        final ScreenshotHardwareBuffer screenshotBuffer = syncScreenCapture.getBuffer();
+        if (screenshotBuffer == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for display=" + mDisplayId);
+            return null;
+        }
         Bitmap screenShot = screenshotBuffer.asBitmap();
         if (screenShot == null) {
-            Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
-                    + mDisplayId);
+            Log.e(LOG_TAG, "Failed to take screenshot for display=" + mDisplayId);
             return null;
         }
         Bitmap swBitmap;
@@ -1263,16 +1265,23 @@
                 ScreenCapture.createSyncCaptureListener();
         try {
             if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) {
+                Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
                 return null;
             }
-
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while taking screenshot!", re);
             return null;
         }
-        ScreenCapture.ScreenshotHardwareBuffer captureBuffer =
-                syncScreenCapture.getBuffer();
+        ScreenCapture.ScreenshotHardwareBuffer captureBuffer = syncScreenCapture.getBuffer();
+        if (captureBuffer == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
+            return null;
+        }
         Bitmap screenShot = captureBuffer.asBitmap();
+        if (screenShot == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
+            return null;
+        }
         Bitmap swBitmap;
         try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) {
             swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index b159321..3927b40 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -781,7 +781,18 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -817,7 +828,18 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index e01543d..a3b202a 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -119,4 +119,8 @@
     void setAssociationTag(int associationId, String tag);
 
     void clearAssociationTag(int associationId);
+
+    byte[] getBackupPayload(int userId);
+
+    void applyRestoredPayload(in byte[] payload, int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 33d37bb..9f14c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3708,12 +3708,12 @@
             "android.hardware.telephony.subscription";
 
     /**
-     * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device is capable of communicating with
-     * other devices via Thread network.
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device is capable of communicating with other devices via
+     * <a href="https://www.threadgroup.org">Thread</a> networking protocol.
      */
     @SdkConstant(SdkConstantType.FEATURE)
-    public static final String FEATURE_THREADNETWORK = "android.hardware.threadnetwork";
+    public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc79..946b5f3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@
 
         /**
          * @param type the type of the credential to be stored
-         * @param credentialData the full credential creation request data
+         * @param credentialData the full credential creation request data, which must at minimum
+         * contain the required fields observed at the
+         * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+         * because they are required for properly displaying the system credential selector UI
          * @param candidateQueryData the partial request data that will be sent to the provider
          *                           during the initial creation candidate query stage
          */
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 706e75e..f2980f4 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1875,7 +1875,7 @@
      * statement
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         try {
             return nativeChanges(mConnectionPtr);
         } finally {
@@ -1887,7 +1887,7 @@
      * Return the total number of database changes made on the current connection.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         try {
             return nativeTotalChanges(mConnectionPtr);
         } finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index a3f8383..5b80e6a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -956,7 +956,7 @@
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * @param path to database file to open and/or create
@@ -1002,7 +1002,7 @@
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
@@ -1163,7 +1163,7 @@
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * @param factory an optional factory class that is called to instantiate a
@@ -1182,7 +1182,7 @@
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      * @param openParams configuration parameters that are used for opening SQLiteDatabase
      * @return a SQLiteDatabase instance
@@ -2208,10 +2208,9 @@
      *
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getLastChangedRowsCount() {
-        return getThreadSession().getLastChangedRowsCount();
+    public long getLastChangedRowCount() {
+        return getThreadSession().getLastChangedRowCount();
     }
 
     /**
@@ -2223,9 +2222,9 @@
      * <code><pre>
      *    database.beginTransaction();
      *    try {
-     *        long initialValue = database.getTotalChangedRowsCount();
+     *        long initialValue = database.getTotalChangedRowCount();
      *        // Execute SQL statements
-     *        long changedRows = database.getTotalChangedRowsCount() - initialValue;
+     *        long changedRows = database.getTotalChangedRowCount() - initialValue;
      *        // changedRows counts the total number of rows updated in the transaction.
      *    } finally {
      *        database.endTransaction();
@@ -2236,10 +2235,9 @@
      *
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getTotalChangedRowsCount() {
-        return getThreadSession().getTotalChangedRowsCount();
+    public long getTotalChangedRowCount() {
+        return getThreadSession().getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index ef1a9cb..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -998,9 +998,9 @@
      * this connection.
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getLastChangedRowsCount();
+        return mConnection.getLastChangedRowCount();
     }
 
     /**
@@ -1008,9 +1008,9 @@
      * it was created.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getTotalChangedRowsCount();
+        return mConnection.getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 889a43c..5ff0e7a 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.GraphicBuffer;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -399,11 +400,14 @@
     public static final @android.annotation.NonNull Parcelable.Creator<HardwareBuffer> CREATOR =
             new Parcelable.Creator<HardwareBuffer>() {
         public HardwareBuffer createFromParcel(Parcel in) {
+            if (in == null) {
+                throw new NullPointerException("null passed to createFromParcel");
+            }
             long nativeObject = nReadHardwareBufferFromParcel(in);
             if (nativeObject != 0) {
                 return new HardwareBuffer(nativeObject);
             }
-            return null;
+            throw new BadParcelableException("Failed to read hardware buffer");
         }
 
         public HardwareBuffer[] newArray(int size) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c2fe080..c80124c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
 import android.hardware.CameraStatus;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
@@ -1730,6 +1731,17 @@
     }
 
     /**
+     * Remaps Camera Ids in the CameraService.
+     *
+     * @hide
+    */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+    public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+            throws CameraAccessException, SecurityException, IllegalArgumentException {
+        CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+    }
+
+    /**
      * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
      * currently active session. Validation is done downstream.
      *
@@ -1802,6 +1814,13 @@
 
         private final Object mLock = new Object();
 
+        /**
+         * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+         * in the CameraService every time we connect to it, including when the CameraService
+         * Binder dies and we reconnect to it.
+         */
+        @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
         // Access only through getCameraService to deal with binder death
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
@@ -1944,6 +1963,41 @@
             } catch (RemoteException e) {
                 // Camera service died in all probability
             }
+
+            if (mActiveCameraIdRemapping != null) {
+                try {
+                    cameraService.remapCameraIds(mActiveCameraIdRemapping);
+                } catch (ServiceSpecificException e) {
+                    // Unexpected failure, ignore and continue.
+                    Log.e(TAG, "Unable to remap camera Ids in the camera service");
+                } catch (RemoteException e) {
+                    // Camera service died in all probability
+                }
+            }
+        }
+
+        /** Updates the cameraIdRemapping state in the CameraService. */
+        public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+                throws CameraAccessException, SecurityException {
+            synchronized (mLock) {
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.remapCameraIds(cameraIdRemapping);
+                    mActiveCameraIdRemapping = cameraIdRemapping;
+                } catch (ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
         }
 
         private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index c111138..36199e5 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -642,6 +642,7 @@
                 try {
                     sTagService = sService.getNfcTagInterface();
                 } catch (RemoteException e) {
+                    sTagService = null;
                     Log.e(TAG, "could not retrieve NFC Tag service");
                     throw new UnsupportedOperationException();
                 }
@@ -650,12 +651,14 @@
                 try {
                     sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
                 } catch (RemoteException e) {
+                    sNfcFCardEmulationService = null;
                     Log.e(TAG, "could not retrieve NFC-F card emulation service");
                     throw new UnsupportedOperationException();
                 }
                 try {
                     sCardEmulationService = sService.getNfcCardEmulationInterface();
                 } catch (RemoteException e) {
+                    sCardEmulationService = null;
                     Log.e(TAG, "could not retrieve card emulation service");
                     throw new UnsupportedOperationException();
                 }
@@ -839,30 +842,54 @@
         // assigning to sService is not thread-safe, but this is best-effort code
         // and on a well-behaved system should never happen
         sService = service;
-        try {
-            sTagService = service.getNfcTagInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC tag service during service recovery");
-            // nothing more can be done now, sService is still stale, we'll hit
-            // this recovery path again later
-            return;
+        if (sHasNfcFeature) {
+            try {
+                sTagService = service.getNfcTagInterface();
+            } catch (RemoteException ee) {
+                sTagService = null;
+                Log.e(TAG, "could not retrieve NFC tag service during service recovery");
+                // nothing more can be done now, sService is still stale, we'll hit
+                // this recovery path again later
+                return;
+            }
         }
 
-        try {
-            sCardEmulationService = service.getNfcCardEmulationInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC card emulation service during service recovery");
-        }
+        if (sHasCeFeature) {
+            try {
+                sCardEmulationService = service.getNfcCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC card emulation service during service recovery");
+            }
 
-        try {
-            sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC-F card emulation service during service recovery");
+            try {
+                sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sNfcFCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC-F card emulation service during service recovery");
+            }
         }
 
         return;
     }
 
+    private boolean isCardEmulationEnabled() {
+        if (sHasCeFeature) {
+            return (sCardEmulationService != null || sNfcFCardEmulationService != null);
+        }
+        return false;
+    }
+
+    private boolean isTagReadingEnabled() {
+        if (sHasNfcFeature) {
+            return sTagService != null;
+        }
+        return false;
+    }
+
+
     /**
      * Return true if this NFC Adapter has any features enabled.
      *
@@ -876,8 +903,9 @@
      * @return true if this NFC Adapter has any features enabled
      */
     public boolean isEnabled() {
+        boolean serviceState = false;
         try {
-            return sService.getState() == STATE_ON;
+            serviceState = sService.getState() == STATE_ON;
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -886,12 +914,12 @@
                 return false;
             }
             try {
-                return sService.getState() == STATE_ON;
+                serviceState = sService.getState() == STATE_ON;
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
-            return false;
         }
+        return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
     }
 
     /**
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 2584f04..22d6fcd 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -182,7 +182,7 @@
      *
      * Asynchronous messages represent interrupts or events that do not require global ordering
      * with respect to synchronous messages.  Asynchronous messages are not subject to
-     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}.
      *
      * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
      * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
@@ -203,7 +203,7 @@
      *
      * Asynchronous messages represent interrupts or events that do not require global ordering
      * with respect to synchronous messages.  Asynchronous messages are not subject to
-     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}.
      *
      * @param callback The callback interface in which to handle messages, or null.
      * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
@@ -751,7 +751,7 @@
         MessageQueue queue = mQueue;
         if (queue == null) {
             RuntimeException e = new RuntimeException(
-                this + " sendMessageAtTime() called with no mQueue");
+                    this + " sendMessageAtFrontOfQueue() called with no mQueue");
             Log.w("Looper", e.getMessage(), e);
             return false;
         }
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 0d1dde1..6b43e73 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -23,4 +23,5 @@
     void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
     void close();
     void sendHint(int hint);
+    void setMode(int mode, boolean enabled);
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 69889c5..6ef1dc0 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -83,3 +83,6 @@
 # DDM Protocol
 per-file DdmSyncState.java = sanglardf@google.com, rpaquay@google.com
 per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
+
+# PerformanceHintManager
+per-file PerformanceHintManager.java = file:/ADPF_OWNERS
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index bcea797..cbc9213 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -51,6 +51,15 @@
     }
 
     /**
+     * Get preferred update rate information for this device.
+     *
+     * @return the preferred update rate supported by device software
+     */
+    public long getPreferredUpdateRateNanos() {
+        return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
+    }
+
+    /**
      * Creates a {@link Session} for the given set of threads and sets their initial target work
      * duration.
      *
@@ -78,35 +87,22 @@
     }
 
     /**
-     * Get preferred update rate information for this device.
-     *
-     * @return the preferred update rate supported by device software
-     */
-    public long getPreferredUpdateRateNanos() {
-        return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
-    }
-
-    /**
      * A Session represents a group of threads with an inter-related workload such that hints for
      * their performance should be considered as a unit. The threads in a given session should be
-     * long-life and not created or destroyed dynamically.
+     * long-lived and not created or destroyed dynamically.
      *
-     * <p>Each session is expected to have a periodic workload with a target duration for each
-     * cycle. The cycle duration is likely greater than the target work duration to allow other
-     * parts of the pipeline to run within the available budget. For example, a renderer thread may
-     * work at 60hz in order to produce frames at the display's frame but have a target work
-     * duration of only 6ms.</p>
+     * The work duration API can be used with periodic workloads to dynamically adjust thread
+     * performance and keep the work on schedule while optimizing the available power budget.
+     * When using the work duration API, the starting target duration should be specified
+     * while creating the session, but can later be adjusted with
+     * {@link #updateTargetWorkDuration(long)}. While using the work duration API, the client is be
+     * expected to call {@link #reportActualWorkDuration(long)} each cycle to report the actual
+     * time taken to complete to the system.
      *
-     * <p>Any call in this class will change its internal data, so you must do your own thread
-     * safety to protect from racing.</p>
+     * Any call in this class will change its internal data, so you must do your own thread
+     * safety to protect from racing.
      *
-     * <p>Note that the target work duration can be {@link #updateTargetWorkDuration(long) updated}
-     * if workloads change.</p>
-     *
-     * <p>After each cycle of work, the client is expected to
-     * {@link #reportActualWorkDuration(long) report} the actual time taken to complete.</p>
-     *
-     * <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
      */
     public static class Session implements Closeable {
         private long mNativeSessionPtr;
@@ -186,9 +182,9 @@
         /**
          * Reports the actual duration for the last cycle of work.
          *
-         * <p>The system will attempt to adjust the core placement of the threads within the thread
+         * The system will attempt to adjust the core placement of the threads within the thread
          * group and/or the frequency of the core on which they are run to bring the actual duration
-         * close to the target duration.</p>
+         * close to the target duration.
          *
          * @param actualDurationNanos how long the thread group took to complete its last task in
          *     nanoseconds
@@ -202,7 +198,7 @@
         /**
          * Ends the current hint session.
          *
-         * <p>Once called, you should not call anything else on this object.</p>
+         * Once called, you should not call anything else on this object.
          */
         public void close() {
             if (mNativeSessionPtr != 0) {
@@ -230,6 +226,16 @@
         }
 
         /**
+         * This tells the session that these threads can be
+         * safely scheduled to prefer power efficiency over performance.
+         *
+         * @param enabled The flag that sets whether this session uses power-efficient scheduling.
+         */
+        public void setPreferPowerEfficiency(boolean enabled) {
+            nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled);
+        }
+
+        /**
          * Set a list of threads to the performance hint session. This operation will replace
          * the current list of threads with the given list of threads.
          * Note that this is not an oneway method.
@@ -275,4 +281,6 @@
     private static native void nativeCloseSession(long nativeSessionPtr);
     private static native void nativeSendHint(long nativeSessionPtr, int hint);
     private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
+    private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
+            boolean enabled);
 }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 180735b..81d4e3a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -1462,7 +1462,7 @@
 
         if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
             // Detect nothing extra
-        } else if (Build.IS_USERDEBUG) {
+        } else if (Build.IS_USERDEBUG || Build.IS_ENG) {
             // Detect everything in bundled apps
             if (isBundledSystemApp(ai)) {
                 builder.detectAll();
@@ -1470,14 +1470,9 @@
                 if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
                     builder.penaltyFlashScreen();
                 }
-            }
-        } else if (Build.IS_ENG) {
-            // Detect everything in bundled apps
-            if (isBundledSystemApp(ai)) {
-                builder.detectAll();
-                builder.penaltyDropBox();
-                builder.penaltyLog();
-                builder.penaltyFlashScreen();
+                if (Build.IS_ENG) {
+                    builder.penaltyLog();
+                }
             }
         }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a565b3b..2e62e03 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17103,6 +17103,12 @@
                 ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) {
             getPublicSettingsForClass(Global.class, allKeys, readableKeys,
                     readableKeysWithMaxTargetSdk);
+            // Add Global.Wearable keys on watches.
+            if (ActivityThread.currentApplication().getApplicationContext().getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                getPublicSettingsForClass(Global.Wearable.class, allKeys, readableKeys,
+                        readableKeysWithMaxTargetSdk);
+            }
         }
 
         /**
@@ -18287,7 +18293,7 @@
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
-        public static class Wearable {
+        public static final class Wearable extends NameValueTable {
             /**
              * Whether the user has any pay tokens on their watch.
              * @hide
@@ -18610,6 +18616,7 @@
              * What OS does paired device has.
              * @hide
              */
+            @Readable
             public static final String PAIRED_DEVICE_OS_TYPE = "paired_device_os_type";
 
             // Possible values of PAIRED_DEVICE_OS_TYPE
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 59b945c..cf3707b 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4905,6 +4905,16 @@
          */
         public static final String COLUMN_SATELLITE_ENABLED = "satellite_enabled";
 
+        /**
+         * TelephonyProvider column name for satellite attach enabled for carrier. The value of this
+         * column is set based on user settings.
+         * By default, it's disabled.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER =
+                "satellite_attach_enabled_for_carrier";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -4974,7 +4984,8 @@
                 COLUMN_USAGE_SETTING,
                 COLUMN_TP_MESSAGE_REF,
                 COLUMN_USER_HANDLE,
-                COLUMN_SATELLITE_ENABLED
+                COLUMN_SATELLITE_ENABLED,
+                COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
         );
 
         /**
diff --git a/core/java/android/service/gatekeeper/OWNERS b/core/java/android/service/gatekeeper/OWNERS
index 2ca52cd..7c4f285 100644
--- a/core/java/android/service/gatekeeper/OWNERS
+++ b/core/java/android/service/gatekeeper/OWNERS
@@ -1,3 +1,2 @@
-swillden@google.com
-jdanis@google.com
-jbires@google.com
+include platform/system/gatekeeper:/OWNERS
+include /services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index e381d17..5b57fc7 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -2,7 +2,6 @@
 
 asalo@google.com
 augale@google.com
-bquezada@google.com
 eejiang@google.com
 payamp@google.com
 siddikap@google.com
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index a6be687..0935ffd9 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -1,5 +1,6 @@
 set noparent
 
+grantapher@google.com
 halilibo@google.com
 haoyuchang@google.com
 justinghan@google.com
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
new file mode 100644
index 0000000..0fe974a
--- /dev/null
+++ b/core/java/android/text/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+          {
+              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          },
+          {
+              "exclude-annotation": "androidx.test.filters.LargeTest"
+          }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index c501f5a..751cd21 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -203,6 +203,7 @@
                             candidateView.getHandwritingDelegatorCallback().run();
                             mState.mHasPreparedHandwritingDelegation = true;
                         } else {
+                            mState.mPendingConnectedView = new WeakReference<>(candidateView);
                             requestFocusWithoutReveal(candidateView);
                         }
                     }
@@ -264,8 +265,9 @@
                 mShowHoverIconForConnectedView = false;
                 return;
             }
-            if (mState != null && mState.mShouldInitHandwriting) {
-                tryStartHandwriting();
+            if (mState != null && mState.mPendingConnectedView != null
+                    && mState.mPendingConnectedView.get() == view) {
+                startHandwriting(view);
             }
         }
     }
@@ -290,40 +292,6 @@
         }
     }
 
-    /**
-     * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
-     * the following 3 conditions should meet:
-     *   a) The stylus movement exceeds the touchSlop.
-     *   b) A View has built InputConnection with IME.
-     *   c) The stylus event lands into the connected View's boundary.
-     * This method will immediately fail without any side effect if condition a or b is not met.
-     * However, if both condition a and b are met but the condition c is not met, it will reset the
-     * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the
-     * next ACTION_DOWN.
-     */
-    private void tryStartHandwriting() {
-        if (!mState.mExceedHandwritingSlop) {
-            return;
-        }
-        final View connectedView = getConnectedView();
-        if (connectedView == null) {
-            return;
-        }
-
-        if (!connectedView.isAutoHandwritingEnabled()) {
-            clearConnectedView();
-            return;
-        }
-
-        final Rect handwritingArea = getViewHandwritingArea(connectedView);
-        if (isInHandwritingArea(
-                handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
-            startHandwriting(connectedView);
-        } else {
-            mState.mShouldInitHandwriting = false;
-        }
-    }
-
     /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
@@ -626,6 +594,7 @@
         private boolean mHasInitiatedHandwriting;
 
         private boolean mHasPreparedHandwritingDelegation;
+
         /**
          * Whether the current ongoing stylus MotionEvent sequence already exceeds the
          * handwriting slop.
@@ -634,6 +603,12 @@
          */
         private boolean mExceedHandwritingSlop;
 
+        /**
+         * A view which has requested focus and is pending input connection creation. When an input
+         * connection is created for the view, a handwriting session should be started for the view.
+         */
+        private WeakReference<View> mPendingConnectedView = null;
+
         /** The pointer id of the stylus pointer that is being tracked. */
         private final int mStylusPointerId;
         /** The time stamp when the stylus pointer goes down. */
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f81dc5a..c35b690 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -37,6 +37,7 @@
 import android.os.Parcelable;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -483,10 +484,12 @@
         mSources = sources;
         mKeyboardType = keyboardType;
         mKeyCharacterMap = keyCharacterMap;
-        if (keyboardLanguageTag != null) {
-            mKeyboardLanguageTag = ULocale
+        if (!TextUtils.isEmpty(keyboardLanguageTag)) {
+            String langTag;
+            langTag = ULocale
                     .createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
                     .toLanguageTag();
+            mKeyboardLanguageTag = TextUtils.equals(langTag, "und") ? null : langTag;
         } else {
             mKeyboardLanguageTag = null;
         }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f497..e673676 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
 import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@
             long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
+    private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@
         callback.accept(fence);
     }
 
+    /**
+     * @hide
+     */
+    public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+        return nativeGetStalledTransactionInfo(pid);
+    }
+
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 2db2132..ad46f2b 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -815,7 +815,7 @@
 
         int syncResult = syncAndDrawFrame(frameInfo);
         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
-            Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
+            Log.w("HWUI", "Surface lost, forcing relayout");
             // We lost our surface. For a relayout next frame which should give us a new
             // surface from WindowManager, which hopefully will work.
             attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e0e8a6b..92509c9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16212,7 +16212,27 @@
         if (fg != null && isVisible != fg.isVisible()) {
             fg.setVisible(isVisible, false);
         }
+        notifyAutofillManagerViewVisibilityChanged(isVisible);
+        if (isVisible != oldVisible) {
+            if (isAccessibilityPane()) {
+                notifyViewAccessibilityStateChangedIfNeeded(isVisible
+                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+            }
 
+            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
+
+            if (!getSystemGestureExclusionRects().isEmpty()) {
+                postUpdate(this::updateSystemGestureExclusionRects);
+            }
+
+            if (!collectPreferKeepClearRects().isEmpty()) {
+                postUpdate(this::updateKeepClearRects);
+            }
+        }
+    }
+
+    private void notifyAutofillManagerViewVisibilityChanged(boolean isVisible) {
         if (isAutofillable()) {
             AutofillManager afm = getAutofillManager();
 
@@ -16236,24 +16256,6 @@
                 }
             }
         }
-
-        if (isVisible != oldVisible) {
-            if (isAccessibilityPane()) {
-                notifyViewAccessibilityStateChangedIfNeeded(isVisible
-                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
-                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
-            }
-
-            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-
-            if (!getSystemGestureExclusionRects().isEmpty()) {
-                postUpdate(this::updateSystemGestureExclusionRects);
-            }
-
-            if (!collectPreferKeepClearRects().isEmpty()) {
-                postUpdate(this::updateKeepClearRects);
-            }
-        }
     }
 
     /**
@@ -22128,6 +22130,8 @@
                     // Invoking onVisibilityAggregated directly here since the subtree
                     // will also receive detached from window
                     onVisibilityAggregated(false);
+                } else {
+                    notifyAutofillManagerViewVisibilityChanged(false);
                 }
             }
         }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3554483..5d1a81f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
@@ -983,7 +982,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Make this public API.
     String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
             "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
 
@@ -1018,7 +1016,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
 
     /**
@@ -1056,7 +1053,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
@@ -1102,7 +1098,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
@@ -1151,7 +1146,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
@@ -1189,7 +1183,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
@@ -1233,7 +1226,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
 
@@ -1300,6 +1292,102 @@
             "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
 
     /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * user-facing aspect ratio compatibility override.
+     *
+     * <p>The compatibility override enables device users to set the app's aspect
+     * ratio or force the app to fill the display regardless of the aspect
+     * ratio or orientation specified in the app manifest.
+     *
+     * <p>The aspect ratio compatibility override is exposed to users in device
+     * settings. A menu in device settings lists all apps that don't opt out of
+     * the compatibility override. Users select apps from the menu and set the
+     * app aspect ratio on a per-app basis. Typically, the menu is available
+     * only on large screen devices.
+     *
+     * <p>When users apply the aspect ratio override, the minimum aspect ratio
+     * specified in the app manifest is overridden. If users choose a
+     * full-screen aspect ratio, the orientation of the activity is forced to
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+     * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+     * disable the full-screen option only.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the user aspect ratio compatibility override, add this property
+     * to your app manifest and set the value to {@code false}. Your app will be excluded
+     * from the list of apps in device settings, and users will not be able to override
+     * the app's aspect ratio.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+    /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * full-screen option of the aspect ratio compatibility override. (For
+     * background information about the aspect ratio compatibility override, see
+     * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+     *
+     * <p>When users apply the aspect ratio compatibility override, the orientation
+     * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+     * override, add this property to your app manifest and set the value to {@code false}.
+     * Your app will have full-screen option removed from the list of user aspect ratio
+     * override options in device settings, and users will not be able to apply
+     * full-screen override to your app.
+     *
+     * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+     * {@code false}, this property has no effect.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index d588c48..f67a61b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -160,6 +160,8 @@
 
     @NonNull
     private final AtomicReference<InputConnection> mInputConnectionRef;
+    @NonNull
+    private final AtomicBoolean mDeactivateRequested = new AtomicBoolean(false);
 
     @NonNull
     private final Looper mLooper;
@@ -211,10 +213,6 @@
         return mInputConnectionRef.get() == null;
     }
 
-    private boolean isActive() {
-        return mParentInputMethodManager.isActive() && !isFinished();
-    }
-
     private View getServedView() {
         return mServedView.get();
     }
@@ -349,25 +347,15 @@
      */
     @Dispatching(cancellable = false)
     public void deactivate() {
-        if (isFinished()) {
+        if (mDeactivateRequested.getAndSet(true)) {
             // This is a small performance optimization.  Still only the 1st call of
-            // reportFinish() will take effect.
+            // deactivate() will take effect.
             return;
         }
         dispatch(() -> {
-            // Note that we do not need to worry about race condition here, because 1) mFinished is
-            // updated only inside this block, and 2) the code here is running on a Handler hence we
-            // assume multiple closeConnection() tasks will not be handled at the same time.
-            if (isFinished()) {
-                return;
-            }
             Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
             try {
                 InputConnection ic = getInputConnection();
-                // Note we do NOT check isActive() here, because this is safe
-                // for an IME to call at any time, and we need to allow it
-                // through to clean up our state after the IME has switched to
-                // another client.
                 if (ic == null) {
                     return;
                 }
@@ -429,7 +417,7 @@
     public String toString() {
         return "RemoteInputConnectionImpl{"
                 + "connection=" + getInputConnection()
-                + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive()
+                + " mDeactivateRequested=" + mDeactivateRequested.get()
                 + " mServedView=" + mServedView.get()
                 + "}";
     }
@@ -464,7 +452,7 @@
     public void dispatchReportFullscreenMode(boolean enabled) {
         dispatch(() -> {
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 return;
             }
             ic.reportFullscreenMode(enabled);
@@ -480,7 +468,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
                 return null;
             }
@@ -502,7 +490,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
                 return null;
             }
@@ -524,7 +512,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSelectedText on inactive InputConnection");
                 return null;
             }
@@ -546,7 +534,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSurroundingText on inactive InputConnection");
                 return null;
             }
@@ -574,7 +562,7 @@
                 return 0;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                 return 0;
             }
@@ -591,7 +579,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getExtractedText on inactive InputConnection");
                 return null;
             }
@@ -608,7 +596,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -625,7 +613,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -641,7 +629,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCompletion on inactive InputConnection");
                 return;
             }
@@ -657,7 +645,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCorrection on inactive InputConnection");
                 return;
             }
@@ -677,7 +665,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setSelection on inactive InputConnection");
                 return;
             }
@@ -693,7 +681,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performEditorAction on inactive InputConnection");
                 return;
             }
@@ -709,7 +697,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                 return;
             }
@@ -725,7 +713,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -746,7 +734,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -763,7 +751,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -780,7 +768,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -809,7 +797,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection");
                 return;
             }
@@ -833,7 +821,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null && !isActive()) {
+            if (ic == null && mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingText on inactive InputConnection");
                 return;
             }
@@ -849,7 +837,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                 return;
             }
@@ -865,7 +853,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                 return;
             }
@@ -882,7 +870,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                 return;
             }
@@ -899,7 +887,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
                 return;
             }
@@ -919,7 +907,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "beginBatchEdit on inactive InputConnection");
                 return;
             }
@@ -935,7 +923,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "endBatchEdit on inactive InputConnection");
                 return;
             }
@@ -951,7 +939,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performSpellCheck on inactive InputConnection");
                 return;
             }
@@ -968,7 +956,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performPrivateCommand on inactive InputConnection");
                 return;
             }
@@ -1006,7 +994,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performHandwritingGesture on inactive InputConnection");
                 if (resultReceiver != null) {
                     resultReceiver.send(
@@ -1046,7 +1034,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "previewHandwritingGesture on inactive InputConnection");
                 return; // cancelled
             }
@@ -1094,7 +1082,7 @@
             @InputConnection.CursorUpdateMode int cursorUpdateMode,
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
-        if (ic == null || !isActive()) {
+        if (ic == null || mDeactivateRequested.get()) {
             Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
@@ -1131,7 +1119,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
                 resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
                 return;
@@ -1160,7 +1148,7 @@
                 return false;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitContent on inactive InputConnection");
                 return false;
             }
@@ -1185,7 +1173,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setImeConsumesInput on inactive InputConnection");
                 return;
             }
@@ -1209,7 +1197,7 @@
                         return; // cancelled
                     }
                     InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
+                    if (ic == null || mDeactivateRequested.get()) {
                         Log.w(TAG, "replaceText on inactive InputConnection");
                         return;
                     }
@@ -1228,7 +1216,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "commitText on inactive InputConnection");
                     return;
                 }
@@ -1248,7 +1236,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "setSelection on inactive InputConnection");
                     return;
                 }
@@ -1265,7 +1253,7 @@
                     return null;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getSurroundingText on inactive InputConnection");
                     return null;
                 }
@@ -1293,7 +1281,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                     return;
                 }
@@ -1309,7 +1297,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                     return;
                 }
@@ -1325,7 +1313,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performEditorAction on inactive InputConnection");
                     return;
                 }
@@ -1341,7 +1329,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                     return;
                 }
@@ -1358,7 +1346,7 @@
                     return 0;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                     return 0;
                 }
@@ -1374,7 +1362,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                     return;
                 }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b0e5f777..7a96fd2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -337,7 +337,7 @@
      *
      * @hide
      */
-    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
 
     /**
      * Application that hosts the remote views.
@@ -4823,7 +4823,7 @@
     public static boolean isAdapterConversionEnabled() {
         return AppGlobals.getIntCoreSetting(
                 SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
     }
 
     /**
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index d4f4d19..a250a86 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -47,7 +47,7 @@
      *
      * @hide
      */
-    private static final int MAX_NUM_ENTRY = 25;
+    private static final int MAX_NUM_ENTRY = 10;
 
     /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index 107cac2..bc71bee 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -45,6 +45,17 @@
           "exclude-annotation": "android.platform.test.annotations.AppModeFull"
         }
       ]
+    },
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ]
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7dbab96..afe7559 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -14995,7 +14995,9 @@
     }
 
     boolean canShare() {
-        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
+        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
+                || !getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_textShareSupported)) {
             return false;
         }
         return canCopy();
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 269ce08..e32adcf 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -106,7 +106,7 @@
      * @see WindowManager#getCurrentWindowMetrics()
      * @see WindowManager#getMaximumWindowMetrics()
      */
-    public static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+    private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
             @NonNull Configuration newConfig) {
         final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
         final Rect newBounds = newConfig.windowConfiguration.getBounds();
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index de0fe25..95e9e86 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -23,6 +23,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,7 +43,8 @@
  */
 public class ScreenCapture {
     private static final String TAG = "ScreenCapture";
-    private static final int SCREENSHOT_WAIT_TIME_S = 1;
+    private static final int SCREENSHOT_WAIT_TIME_S = 4 * Build.HW_TIMEOUT_MULTIPLIER;
+
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
             long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
@@ -767,7 +769,10 @@
             @Override
             public ScreenshotHardwareBuffer getBuffer() {
                 try {
-                    latch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+                    if (!latch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS)) {
+                        Log.e(TAG, "Timed out waiting for screenshot results");
+                        return null;
+                    }
                     return bufferRef[0];
                 } catch (Exception e) {
                     Log.e(TAG, "Failed to wait for screen capture result", e);
@@ -791,6 +796,7 @@
          * Get the {@link ScreenshotHardwareBuffer} synchronously. This can be null if the
          * screenshot failed or if there was no callback in {@link #SCREENSHOT_WAIT_TIME_S} seconds.
          */
+        @Nullable
         public abstract ScreenshotHardwareBuffer getBuffer();
     }
 }
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 0ea8014..4261a0f 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -234,6 +234,23 @@
     }
 
     /**
+     * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+     * status.
+     *
+     * @param listener to receive visual service start/stop events.
+     */
+    public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+            listener) {
+        try {
+            if (mVoiceInteractionManagerService != null) {
+                mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to register visual query detection start listener", e);
+        }
+    }
+
+    /**
      * Enables visual detection service.
      *
      * @param listener to receive visual attention gained/lost events.
diff --git a/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
new file mode 100644
index 0000000..cc49a75
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.app;
+
+
+ oneway interface IVisualQueryRecognitionStatusListener {
+    /**
+     * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+     */
+    void onStartPerceiving();
+
+    /**
+     * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StopPerceiving}.
+     */
+    void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc..314ed69 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -325,6 +326,9 @@
     void shutdownHotwordDetectionService();
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+    void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
+    @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
     void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263b..b3b0603 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@
     public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
             List<InputMethodSubtype> list) {
         Set<LocaleInfo> imeLocales = new HashSet<>();
+        Set<String> languageTagSet = new HashSet<>();
         for (InputMethodSubtype subtype : list) {
-            Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
-            LocaleInfo cacheInfo  = getLocaleInfo(locale, sLocaleCache);
-            LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
-            localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
-            imeLocales.add(localeInfo);
+            String languageTag = subtype.getLanguageTag();
+            if (!languageTagSet.contains(languageTag)) {
+                languageTagSet.add(languageTag);
+                Locale locale = Locale.forLanguageTag(languageTag);
+                LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+                LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+                imeLocales.add(localeInfo);
+            }
         }
         return imeLocales;
     }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index a2c4b23..0a69ea8 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -534,7 +534,14 @@
     /**
      * (boolean) Whether to enable the adapter conversion in RemoteViews
      */
-    public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+    public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+            "CursorControlFeature__remoteviews_adapter_conversion";
+
+    /**
+     * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+     */
+    public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+            "systemui__remoteviews_adapter_conversion";
 
     /**
      * Default value for whether the adapter conversion is enabled or not. This is set for
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..7dda91d 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
         updateContentEndPaddings();
     }
 
-    @RemotableViewMethod
+    /**
+     * Set conversation data
+     * @param extras Bundle contains conversation data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -393,8 +397,7 @@
                 = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
 
         // mUser now set (would be nice to avoid the side effect but WHATEVER)
-        setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         // Append remote input history to newMessages (again, side effect is lame but WHATEVS)
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,30 @@
 
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        // bind it, baby
-        bind(newMessages, newHistoricMessages, showSpinner);
-
         int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
-        setUnreadCount(unreadCount);
+
+        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+        // if they exist
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, false /* isHistoric */);
+        final List<MessagingMessage> newHistoricMessagingMessages =
+                createMessages(newHistoricMessages, true /* isHistoric */);
+        // bind it, baby
+        bindViews(user, showSpinner, unreadCount,
+                newMessagingMessages,
+                newHistoricMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains conversation data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -436,15 +458,17 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
-        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
-        // if they exist
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
 
+    private void bindViews(Person user,
+            boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
+            List<MessagingMessage> newHistoricMessagingMessages) {
+        setUser(user);
+        setUnreadCount(unreadCount);
+        bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+    }
+
+    private void bind(boolean showSpinner, List<MessagingMessage> messages,
+            List<MessagingMessage> historicMessages) {
         // Copy our groups, before they get clobbered
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
 
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index de10bd2..0704cb8 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -63,6 +63,8 @@
     public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+        setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
     }
 
     @Override
@@ -83,8 +85,8 @@
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
                 .setUseLineSpacingFromFallbacks(true)
-                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
-                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+                .setBreakStrategy(getBreakStrategy())
+                .setHyphenationFrequency(getHyphenationFrequency());
         int maxLines;
         if (mMaxLinesForHeight > 0) {
             maxLines = mMaxLinesForHeight;
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..8345c5c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
         mConversationTitle = conversationTitle;
     }
 
-    @RemotableViewMethod
+    /**
+     * Set Messaging data
+     * @param extras Bundle contains messaging data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -168,9 +172,28 @@
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
         addRemoteInputHistoryToMessages(newMessages, history);
+
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        bind(newMessages, newHistoricMessages, showSpinner);
+
+        final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
+                true /* isHistoric */);
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, false /* isHistoric */);
+        bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains messaging data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -195,14 +218,15 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
+    private void bindViews(Person user, boolean showSpinner,
+            List<MessagingMessage> historicMessagingMessages,
+            List<MessagingMessage> newMessagingMessages) {
+        setUser(user);
+        bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
 
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
-
+    private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
         addMessagesToGroups(historicMessages, messages, showSpinner);
 
diff --git a/core/java/com/android/server/backup/CompanionBackupHelper.java b/core/java/com/android/server/backup/CompanionBackupHelper.java
new file mode 100644
index 0000000..ef247c2
--- /dev/null
+++ b/core/java/com/android/server/backup/CompanionBackupHelper.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.backup;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BlobBackupHelper;
+import android.companion.ICompanionDeviceManager;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * CDM backup and restore helper.
+ */
+public class CompanionBackupHelper extends BlobBackupHelper {
+
+    private static final String TAG = "CompanionBackupHelper";
+
+    // current schema of the backup state blob
+    private static final int BLOB_VERSION = 1;
+
+    // key under which the CDM data blob is committed to back up
+    private static final String KEY_COMPANION = "companion";
+
+    @UserIdInt
+    private final int mUserId;
+
+    public CompanionBackupHelper(int userId) {
+        super(BLOB_VERSION, KEY_COMPANION);
+
+        mUserId = userId;
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        byte[] payload = null;
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                payload = cdm.getBackupPayload(mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error getting backup from CompanionDeviceManager.", e);
+            }
+        }
+        return payload;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        Slog.i(TAG, "Got companion backup data.");
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                cdm.applyRestoredPayload(payload, mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error applying restored payload to CompanionDeviceManager.", e);
+            }
+        }
+    }
+}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index dd43527..3aca751 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -107,3 +107,6 @@
 
 # SQLite
 per-file android_database_SQLite* = file:/SQLITE_OWNERS
+
+# PerformanceHintManager
+per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 5fcc46e..2ea2158d 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -203,10 +203,10 @@
     Parcel* parcel = parcelForJavaObject(env, in);
     if (parcel) {
         sp<GraphicBuffer> buffer = new GraphicBuffer();
-        parcel->read(*buffer);
-        return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        if (parcel->read(*buffer) == STATUS_OK) {
+            return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        }
     }
-
     return NULL;
 }
 
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index ffe844d..27c4cd4 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -34,26 +34,28 @@
 struct APerformanceHintSession;
 
 typedef APerformanceHintManager* (*APH_getManager)();
+typedef int64_t (*APH_getPreferredUpdateRateNanos)(APerformanceHintManager* manager);
 typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
                                                       size_t, int64_t);
-typedef int64_t (*APH_getPreferredUpdateRateNanos)(APerformanceHintManager* manager);
 typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_closeSession)(APerformanceHintSession* session);
 typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
 typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
 typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
+typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
 APH_getPreferredUpdateRateNanos gAPH_getPreferredUpdateRateNanosFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
 APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
 APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
 APH_closeSession gAPH_closeSessionFn = nullptr;
 APH_sendHint gAPH_sendHintFn = nullptr;
 APH_setThreads gAPH_setThreadsFn = nullptr;
 APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
+APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -65,10 +67,6 @@
     LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
                         "Failed to find required symbol APerformanceHint_getManager!");
 
-    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_createSession!");
-
     gAPH_getPreferredUpdateRateNanosFn =
             (APH_getPreferredUpdateRateNanos)dlsym(handle_,
                                                    "APerformanceHint_getPreferredUpdateRateNanos");
@@ -76,6 +74,10 @@
                         "Failed to find required symbol "
                         "APerformanceHint_getPreferredUpdateRateNanos!");
 
+    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_createSession!");
+
     gAPH_updateTargetWorkDurationFn =
             (APH_updateTargetWorkDuration)dlsym(handle_,
                                                 "APerformanceHint_updateTargetWorkDuration");
@@ -96,8 +98,7 @@
 
     gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
     LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
-                        "Failed to find required symbol "
-                        "APerformanceHint_sendHint!");
+                        "Failed to find required symbol APerformanceHint_sendHint!");
 
     gAPH_setThreadsFn = (APH_setThreads)dlsym(handle_, "APerformanceHint_setThreads");
     LOG_ALWAYS_FATAL_IF(gAPH_setThreadsFn == nullptr,
@@ -107,6 +108,13 @@
     LOG_ALWAYS_FATAL_IF(gAPH_getThreadIdsFn == nullptr,
                         "Failed to find required symbol APerformanceHint_getThreadIds!");
 
+    gAPH_setPreferPowerEfficiencyFn =
+            (APH_setPreferPowerEfficiency)dlsym(handle_,
+                                                "APerformanceHint_setPreferPowerEfficiency");
+    LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
+                        "Failed to find required symbol"
+                        "APerformanceHint_setPreferPowerEfficiency!");
+
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -223,6 +231,13 @@
     return jintArr;
 }
 
+static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+                                           jboolean enabled) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_setPreferPowerEfficiencyFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                    enabled);
+}
+
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -233,6 +248,7 @@
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
+        {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4249253..dbe0338 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -257,6 +257,14 @@
     jmethodID onTrustedPresentationChanged;
 } gTrustedPresentationCallbackClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID layerName;
+    jfieldID bufferId;
+    jfieldID frameNumber;
+} gStalledTransactionInfoClassInfo;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2032,6 +2040,29 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
 }
 
+static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint pid) {
+    std::optional<gui::StalledTransactionInfo> stalledTransactionInfo =
+            SurfaceComposerClient::getStalledTransactionInfo(pid);
+    if (!stalledTransactionInfo) {
+        return nullptr;
+    }
+
+    jobject jStalledTransactionInfo = env->NewObject(gStalledTransactionInfoClassInfo.clazz,
+                                                     gStalledTransactionInfoClassInfo.ctor);
+    if (!jStalledTransactionInfo) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    env->SetObjectField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.layerName,
+                        env->NewStringUTF(String8{stalledTransactionInfo->layerName}));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.bufferId,
+                      static_cast<jlong>(stalledTransactionInfo->bufferId));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.frameNumber,
+                      static_cast<jlong>(stalledTransactionInfo->frameNumber));
+    return jStalledTransactionInfo;
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2281,6 +2312,8 @@
     {"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
             (void*)nativeCreateTpc},
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
+    {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
+            (void*) nativeGetStalledTransactionInfo },
         // clang-format on
 };
 
@@ -2524,6 +2557,18 @@
     gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
             GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
                              "(Z)V");
+
+    jclass stalledTransactionInfoClazz = FindClassOrDie(env, "android/gui/StalledTransactionInfo");
+    gStalledTransactionInfoClassInfo.clazz = MakeGlobalRefOrDie(env, stalledTransactionInfoClazz);
+    gStalledTransactionInfoClassInfo.ctor =
+            GetMethodIDOrDie(env, stalledTransactionInfoClazz, "<init>", "()V");
+    gStalledTransactionInfoClassInfo.layerName =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "layerName", "Ljava/lang/String;");
+    gStalledTransactionInfoClassInfo.bufferId =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "bufferId", "J");
+    gStalledTransactionInfoClassInfo.frameNumber =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
+
     return err;
 }
 
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index da308b2..149e57a 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -130,6 +130,7 @@
 static install_status_t
 copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
 {
+    static const size_t kPageSize = getpagesize();
     void** args = reinterpret_cast<void**>(arg);
     jstring* javaNativeLibPath = (jstring*) args[0];
     jboolean extractNativeLibs = *(jboolean*) args[1];
@@ -162,9 +163,9 @@
             return INSTALL_FAILED_INVALID_APK;
         }
 
-        if (offset % PAGE_SIZE != 0) {
-            ALOGE("Library '%s' is not page-aligned - will not be able to open it directly from"
-                " apk.\n", fileName);
+        if (offset % kPageSize != 0) {
+            ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+                  "from apk.\n", fileName, kPageSize);
             return INSTALL_FAILED_INVALID_APK;
         }
 
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index a950a79..34ccb48 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -56,6 +56,7 @@
   repeated Target targets = 8;
   optional int32 flags = 9;
   optional int64 abort_time_ns = 10;
+  optional int64 starting_window_remove_time_ns = 11;
 }
 
 message Target {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 73a1abc..72333fb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5372,6 +5372,12 @@
     <!-- Default value for performant auth feature. -->
     <bool name="config_performantAuthDefault">false</bool>
 
+    <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+         fingerprint and face. If a dual-modality device only enrolled a single biometric and
+         experiences high FRR (above threshold), system notification will be sent to encourage user
+         to enroll the other eligible biometric. -->
+    <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
@@ -6592,6 +6598,10 @@
          for the non-customized ones. -->
     <string name="config_hapticFeedbackCustomizationFile" />
 
+    <!-- Enables or disables the "Share" action item shown in the context menu that appears upon
+        long-pressing on selected text. Enabled by default. -->
+    <bool name="config_textShareSupported">true</bool>
+
     <!-- Whether or not ActivityManager PSS profiling is disabled. -->
     <bool name="config_am_disablePssProfiling">false</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d828f33..c5aa8b0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1837,6 +1837,8 @@
     <string name="fingerprint_error_not_match">Fingerprint not recognized</string>
     <!-- Message shown when UDFPS fails to match -->
     <string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
+    <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
+    <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
 
     <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
     <string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b14bf5b..0a0dc36 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2594,6 +2594,9 @@
   <java-symbol type="string" name="biometric_error_device_not_secured" />
   <java-symbol type="string" name="biometric_error_generic" />
 
+  <!-- Biometric FRR config -->
+  <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
   <!-- Device credential strings for BiometricManager -->
   <java-symbol type="string" name="screen_lock_app_setting_name" />
   <java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2607,6 +2610,7 @@
   <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
   <java-symbol type="string" name="fingerprint_error_not_match" />
   <java-symbol type="string" name="fingerprint_udfps_error_not_match" />
+  <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" />
   <java-symbol type="string" name="fingerprint_acquired_partial" />
   <java-symbol type="string" name="fingerprint_acquired_insufficient" />
   <java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -3049,6 +3053,7 @@
   <java-symbol type="id" name="addToDictionaryButton" />
   <java-symbol type="id" name="deleteButton" />
   <!-- TextView -->
+  <java-symbol type="bool" name="config_textShareSupported" />
   <java-symbol type="string" name="failed_to_copy_to_clipboard" />
 
   <java-symbol type="id" name="notification_material_reply_container" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c904d96..91c4dde 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -472,8 +472,10 @@
         final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
         assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
 
-        // Ensure changes in window configuration bounds are reported
-        assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+        // Ensure that Activity#onConfigurationChanged() not be called because the changes in
+        // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
+        // update in transaction.
+        assertEquals(numOfConfig, activity.mNumOfConfigChanges);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index fc72f61..4ee987b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -180,8 +180,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -205,8 +205,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -214,4 +214,21 @@
             mDatabase.endTransaction();
         }
     }
+
+    @Test
+    public void testAutomaticCountersOutsideTransactions() {
+        try {
+            mDatabase.getLastChangedRowCount();
+            fail("getLastChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+
+        try {
+            mDatabase.getTotalChangedRowCount();
+            fail("getTotalChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index f2d6ff8..8b333f3 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -5,4 +5,7 @@
 per-file *Vibrat*.java = file:/services/core/java/com/android/server/vibrator/OWNERS
 
 # Power
-per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
\ No newline at end of file
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
+
+# PerformanceHintManager
+per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index b0826ab..20ba427 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -173,4 +173,13 @@
             session.setThreads(new int[]{-1});
         });
     }
+
+    @Test
+    public void testSetPreferPowerEfficiency() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.setPreferPowerEfficiency(false);
+        s.setPreferPowerEfficiency(true);
+        s.setPreferPowerEfficiency(true);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8028b14..f1eef75 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -221,8 +221,10 @@
 
     @Test
     public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+        // The stylus down point is between mTestView1 and  mTestView2, but it is within the
+        // extended handwriting area of both views. It is closer to mTestView1.
         final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
-        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+        final int y1 = sHwArea1.bottom + (sHwArea2.top - sHwArea1.bottom) / 3;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -231,10 +233,14 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // First create InputConnection for mTestView2 and verify that handwriting is not started.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+        // Next create InputConnection for mTextView1. Handwriting is started for this view since
+        // the stylus down point is closest to this view.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        verify(mHandwritingInitiator).startHandwriting(mTestView1);
     }
 
     @Test
diff --git a/core/tests/packagemonitortests/Android.bp b/core/tests/packagemonitortests/Android.bp
index 453b476..7b5d7df 100644
--- a/core/tests/packagemonitortests/Android.bp
+++ b/core/tests/packagemonitortests/Android.bp
@@ -24,6 +24,9 @@
 android_test {
     name: "FrameworksCorePackageMonitorTests",
     srcs: ["src/**/*.java"],
+    exclude_srcs: [
+        "src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java",
+    ],
     static_libs: [
         "androidx.test.runner",
         "compatibility-device-util-axt",
@@ -39,3 +42,19 @@
         ":TestVisibilityApp",
     ],
 }
+
+android_test {
+    name: "FrameworksCorePackageMonitorWithoutPermissionTests",
+    srcs: ["src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java"],
+    manifest: "AndroidManifestNoPermission.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "frameworks-base-testutils",
+    ],
+    libs: ["android.test.runner"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+    test_config: "AndroidTestWithoutPermission.xml",
+}
diff --git a/core/tests/packagemonitortests/AndroidManifestNoPermission.xml b/core/tests/packagemonitortests/AndroidManifestNoPermission.xml
new file mode 100644
index 0000000..5d6308a
--- /dev/null
+++ b/core/tests/packagemonitortests/AndroidManifestNoPermission.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.frameworks.packagemonitor.withoutpermission">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.packagemonitor.withoutpermission"
+            android:label="Frameworks PackageMonitor Core without Permission Tests" />
+</manifest>
diff --git a/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml b/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml
new file mode 100644
index 0000000..37a6a2f
--- /dev/null
+++ b/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 Core Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="FrameworksCorePackageMonitorWithoutPermissionTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksCorePackageMonitorWithoutPermissionTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.packagemonitor.withoutpermission" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java
new file mode 100644
index 0000000..659c0c2
--- /dev/null
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.withoutpermission;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.PackageMonitor;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * A test to verify PackageMonitor implementation without INTERACT_ACROSS_USERS_FULL permission.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PackageMonitorPermissionTest {
+
+    @Test
+    public void testPackageMonitorNoCrossUserPermission() throws Exception {
+        TestVisibilityPackageMonitor testPackageMonitor = new TestVisibilityPackageMonitor();
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        assertThrows(SecurityException.class,
+                () -> testPackageMonitor.register(context, UserHandle.ALL,
+                        new Handler(Looper.getMainLooper())));
+    }
+
+    private static class TestVisibilityPackageMonitor extends PackageMonitor {
+    }
+}
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e278c52..dcc9686 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -37,6 +37,7 @@
         <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 87e6b18..6057852 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -691,6 +691,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-1449515133": {
+      "message": "Content Recording: stopping active projection for display %d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1443029505": {
       "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
       "level": "INFO",
@@ -1285,6 +1291,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-921346089": {
+      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-917215012": {
       "message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
       "level": "WARN",
@@ -2233,12 +2245,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-88873335": {
-      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
-      "level": "ERROR",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "-87705714": {
       "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
       "level": "VERBOSE",
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING
index abeaf19..8afc30d 100644
--- a/graphics/TEST_MAPPING
+++ b/graphics/TEST_MAPPING
@@ -7,6 +7,18 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ],
+      "file_patterns": ["(/|^)Typeface\\.java", "(/|^)Paint\\.java"]
     }
   ]
 }
diff --git a/graphics/java/android/graphics/TEST_MAPPING b/graphics/java/android/graphics/TEST_MAPPING
new file mode 100644
index 0000000..df91222
--- /dev/null
+++ b/graphics/java/android/graphics/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ],
+      "file_patterns": [
+        "Typeface\\.java",
+        "Paint\\.java",
+        "[^/]*Canvas\\.java",
+        "[^/]*Font[^/]*\\.java"
+      ]
+    }
+  ]
+}
diff --git a/graphics/java/android/graphics/fonts/TEST_MAPPING b/graphics/java/android/graphics/fonts/TEST_MAPPING
new file mode 100644
index 0000000..99cbfe7
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/graphics/java/android/graphics/text/TEST_MAPPING b/graphics/java/android/graphics/text/TEST_MAPPING
new file mode 100644
index 0000000..99cbfe7
--- /dev/null
+++ b/graphics/java/android/graphics/text/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 25f5dec..b4d8def 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -36,6 +36,7 @@
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.WrappedKeyEntry;
 import android.system.keystore2.AuthenticatorSpec;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreSecurityLevel;
 import android.system.keystore2.KeyDescriptor;
@@ -966,6 +967,32 @@
             authenticatorSpecs.add(authSpec);
         }
 
+        if (parts.length > 2) {
+            @KeyProperties.EncryptionPaddingEnum int padding =
+                    KeyProperties.EncryptionPadding.toKeymaster(parts[2]);
+            if (padding == KeymasterDefs.KM_PAD_RSA_OAEP
+                    && response.metadata != null
+                    && response.metadata.authorizations != null) {
+                Authorization[] keyCharacteristics = response.metadata.authorizations;
+
+                for (Authorization authorization : keyCharacteristics) {
+                    // Add default MGF1 digest SHA-1
+                    // when wrapping key has KM_TAG_RSA_OAEP_MGF_DIGEST tag
+                    if (authorization.keyParameter.tag
+                            == KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST) {
+                        // Default MGF1 digest is SHA-1
+                        // and KeyMint only supports default MGF1 digest crypto operations
+                        // for importWrappedKey.
+                        args.add(KeyStore2ParameterUtils.makeEnum(
+                                KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
+                        ));
+                        break;
+                    }
+                }
+            }
+        }
+
         try {
             securityLevel.importWrappedKey(
                     wrappedKey, wrappingkey,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3d72963..079cfa3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -831,7 +831,8 @@
             return true;
         }
 
-        if (!isOnReparent && getContainerWithActivity(activity) == null
+        final TaskFragmentContainer container = getContainerWithActivity(activity);
+        if (!isOnReparent && container == null
                 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
             // We can't find the new launched activity in any recorded container, but it is
             // currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -843,11 +844,21 @@
             return true;
         }
 
-        final TaskFragmentContainer container = getContainerWithActivity(activity);
-        if (!isOnReparent && container != null
-                && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+        // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (container != null) {
+            final TaskContainer taskContainer = container.getTaskContainer();
+            if (taskContainer.isTaskFragmentContainerPinned(container)) {
+                return true;
+            }
+        }
+
+        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+        if (!isOnReparent && taskContainer != null
+                && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                         != container) {
-            // Do not resolve if the launched activity is not the top-most container in the Task.
+            // Do not resolve if the launched activity is not the top-most container (excludes
+            // the pinned container) in the Task.
             return true;
         }
 
@@ -1244,6 +1255,19 @@
     @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+        // Skip resolving if started from pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (launchingActivity != null) {
+            final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+                    launchingActivity);
+            final TaskContainer taskContainer =
+                    taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+            if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+                    taskFragmentContainer)) {
+                return null;
+            }
+        }
+
         /*
          * We will check the following to see if there is any embedding rule matched:
          * 1. Whether the new activity intent should always expand.
@@ -1584,6 +1608,13 @@
             return;
         }
 
+        // If the secondary container is pinned, it should not be removed.
+        final SplitContainer activeContainer =
+                getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+        if (activeContainer instanceof SplitPinContainer) {
+            return;
+        }
+
         existingSplitContainer.getSecondaryContainer().finish(
                 false /* shouldFinishDependent */, mPresenter, wct, this);
     }
@@ -1625,12 +1656,7 @@
             // background.
             return;
         }
-        final SplitContainer splitContainer = getActiveSplitForContainer(container);
-        if (splitContainer instanceof SplitPinContainer
-                && updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */)) {
-            // A SplitPinContainer exists and is updated.
-            return;
-        }
+
         if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
@@ -1643,6 +1669,7 @@
             // If the info is not available yet the task fragment will be expanded when it's ready
             return;
         }
+        final SplitContainer splitContainer = getActiveSplitForContainer(container);
         if (splitContainer == null) {
             return;
         }
@@ -1826,6 +1853,10 @@
             // Don't launch placeholder for primary split container.
             return false;
         }
+        if (splitContainer instanceof SplitPinContainer) {
+            // Don't launch placeholder if pinned
+            return false;
+        }
         return true;
     }
 
@@ -2080,8 +2111,9 @@
      * Returns {@code true} if an Activity with the provided component name should always be
      * expanded to occupy full task bounds. Such activity must not be put in a split.
      */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+    boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof ActivityRule)) {
                 continue;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index fa1eb9e..16d8cb4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -179,8 +179,16 @@
 
     @Nullable
     TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+        return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+    }
+
+    @Nullable
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
         for (int i = mContainers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = mContainers.get(i);
+            if (!includePin && isTaskFragmentContainerPinned(container)) {
+                continue;
+            }
             if (!container.isFinished()) {
                 return container;
             }
@@ -266,6 +274,11 @@
         return mSplitPinContainer;
     }
 
+    boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        return mSplitPinContainer != null
+                && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+    }
+
     void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
         mContainers.add(taskFragmentContainer);
         onTaskFragmentContainerUpdated();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index b504b0c..d440a3e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -595,6 +595,18 @@
     }
 
     @Test
+    public void testResolveStartActivityIntent_skipIfPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        final Intent intent = new Intent();
+        setupSplitRule(mActivity, intent);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+        assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+                mActivity));
+    }
+
+    @Test
     public void testPlaceActivityInTopContainer() {
         mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
@@ -1044,6 +1056,29 @@
     }
 
     @Test
+    public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final Activity pinnedActivity = createMockActivity();
+        final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+                TASK_ID);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+        // No need to handle when the new launched activity is in a pinned TaskFragment.
+        assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+                false /* isOnReparent */));
+        verify(mSplitController, never()).shouldExpand(any(), any());
+
+        // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+        // (e.g. the top-most TaskFragment is pinned)
+        mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+                false /* isOnReparent */);
+        verify(mSplitController).shouldExpand(any(), any());
+    }
+
+    @Test
     public void testGetPlaceholderOptions() {
         // Setup to make sure a transaction record is started.
         mTransactionManager.startNewTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 9bf3b80..42dc19c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -52,12 +52,13 @@
 
     /**
      * Ensures the back animation background color layer is present.
+     *
      * @param startRect The start bounds of the closing target.
      * @param color The background color.
      * @param transaction The animation transaction.
      */
-    void ensureBackground(Rect startRect, int color,
-            @NonNull SurfaceControl.Transaction transaction) {
+    public void ensureBackground(
+            Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
         if (mBackgroundSurface != null) {
             return;
         }
@@ -81,7 +82,12 @@
         mIsRequestingStatusBarAppearance = false;
     }
 
-    void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+    /**
+     * Remove the back animation background.
+     *
+     * @param transaction The animation transaction.
+     */
+    public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
         if (mBackgroundSurface == null) {
             return;
         }
@@ -93,11 +99,21 @@
         mIsRequestingStatusBarAppearance = false;
     }
 
+    /**
+     * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress.
+     *
+     * @param customizer The {@link StatusBarCustomizer} to be used.
+     */
     void setStatusBarCustomizer(StatusBarCustomizer customizer) {
         mCustomizer = customizer;
     }
 
-    void onBackProgressed(float progress) {
+    /**
+     * Update back animation background with for the progress.
+     *
+     * @param progress Progress value from {@link android.window.BackProgressAnimator}
+     */
+    public void onBackProgressed(float progress) {
         if (mCustomizer == null || mStartBounds.isEmpty()) {
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index bb543f2..3790f04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -25,6 +25,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.content.ContentResolver;
@@ -43,7 +44,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
-import android.util.SparseArray;
 import android.view.IRemoteAnimationRunner;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
@@ -70,6 +70,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -113,7 +114,11 @@
     private boolean mShouldStartOnNextMoveEvent = false;
     /** @see #setTriggerBack(boolean) */
     private boolean mTriggerBack;
-    private FlingAnimationUtils mFlingAnimationUtils;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+
+    /** Registry for the back animations */
+    private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
 
     @Nullable
     private BackNavigationInfo mBackNavigationInfo;
@@ -135,13 +140,9 @@
 
     private final TouchTracker mTouchTracker = new TouchTracker();
 
-    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
     @Nullable
     private IOnBackInvokedCallback mActiveCallback;
 
-    private CrossActivityAnimation mDefaultActivityAnimation;
-    private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
     @VisibleForTesting
     final RemoteCallback mNavigationObserver = new RemoteCallback(
             new RemoteCallback.OnResultListener() {
@@ -169,10 +170,18 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context,
-            @NonNull BackAnimationBackground backAnimationBackground) {
-        this(shellInit, shellController, shellExecutor, backgroundHandler,
-                ActivityTaskManager.getService(), context, context.getContentResolver(),
-                backAnimationBackground);
+            @NonNull BackAnimationBackground backAnimationBackground,
+            ShellBackAnimationRegistry shellBackAnimationRegistry) {
+        this(
+                shellInit,
+                shellController,
+                shellExecutor,
+                backgroundHandler,
+                ActivityTaskManager.getService(),
+                context,
+                context.getContentResolver(),
+                backAnimationBackground,
+                shellBackAnimationRegistry);
     }
 
     @VisibleForTesting
@@ -182,8 +191,10 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context, ContentResolver contentResolver,
-            @NonNull BackAnimationBackground backAnimationBackground) {
+            Context context,
+            ContentResolver contentResolver,
+            @NonNull BackAnimationBackground backAnimationBackground,
+            ShellBackAnimationRegistry shellBackAnimationRegistry) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -197,11 +208,7 @@
                 .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
                 .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                 .build();
-    }
-
-    @VisibleForTesting
-    void setEnableUAnimation(boolean enable) {
-        IS_U_ANIMATION_ENABLED = enable;
+        mShellBackAnimationRegistry = shellBackAnimationRegistry;
     }
 
     private void onInit() {
@@ -209,26 +216,6 @@
         createAdapter();
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
-
-        initBackAnimationRunners();
-    }
-
-    private void initBackAnimationRunners() {
-        if (!IS_U_ANIMATION_ENABLED) {
-            return;
-        }
-
-        final CrossTaskBackAnimation crossTaskAnimation =
-                new CrossTaskBackAnimation(mContext, mAnimationBackground);
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
-                crossTaskAnimation.mBackAnimationRunner);
-        mDefaultActivityAnimation =
-                new CrossActivityAnimation(mContext, mAnimationBackground);
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                mDefaultActivityAnimation.mBackAnimationRunner);
-        mCustomizeActivityAnimation =
-                new CustomizeActivityAnimation(mContext, mAnimationBackground);
-        // TODO (236760237): register dialog close animation when it's completed.
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -359,11 +346,11 @@
 
     void registerAnimation(@BackNavigationInfo.BackTargetType int type,
             @NonNull BackAnimationRunner runner) {
-        mAnimationDefinition.set(type, runner);
+        mShellBackAnimationRegistry.registerAnimation(type, runner);
     }
 
     void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
-        mAnimationDefinition.remove(type);
+        mShellBackAnimationRegistry.unregisterAnimation(type);
     }
 
     /**
@@ -434,9 +421,7 @@
         final int backType = backNavigationInfo.getType();
         final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
         if (shouldDispatchToAnimator) {
-            if (mAnimationDefinition.contains(backType)) {
-                mAnimationDefinition.get(backType).startGesture();
-            } else {
+            if (!mShellBackAnimationRegistry.startGesture(backType)) {
                 mActiveCallback = null;
             }
         } else {
@@ -459,6 +444,7 @@
         sendBackEvent(KeyEvent.ACTION_UP);
     }
 
+    @SuppressLint("MissingPermission")
     private void sendBackEvent(int action) {
         final long when = SystemClock.uptimeMillis();
         final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
@@ -671,21 +657,17 @@
         }
 
         final int backType = mBackNavigationInfo.getType();
-        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
         // Simply trigger and finish back navigation when no animator defined.
-        if (!shouldDispatchToAnimator() || runner == null) {
+        if (!shouldDispatchToAnimator()
+                || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
             invokeOrCancelBack();
             return;
-        }
-        if (runner.isWaitingAnimation()) {
+        } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
             ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
             // Supposed it is in post commit animation state, and start the timeout to watch
             // if the animation is ready.
             mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
             return;
-        } else if (runner.isAnimationCancelled()) {
-            invokeOrCancelBack();
-            return;
         }
         startPostCommitAnimation();
     }
@@ -737,12 +719,7 @@
         mShouldStartOnNextMoveEvent = false;
         mTouchTracker.reset();
         mActiveCallback = null;
-        // reset to default
-        if (mDefaultActivityAnimation != null
-                && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
-            mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                    mDefaultActivityAnimation.mBackAnimationRunner);
-        }
+        mShellBackAnimationRegistry.resetDefaultCrossActivity();
         if (mBackNavigationInfo != null) {
             mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
             mBackNavigationInfo = null;
@@ -750,86 +727,88 @@
         mTriggerBack = false;
     }
 
-    private BackAnimationRunner getAnimationRunnerAndInit() {
-        int type = mBackNavigationInfo.getType();
-        // Initiate customized cross-activity animation, or fall back to cross activity animation
-        if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
-            final BackNavigationInfo.CustomAnimationInfo animationInfo =
-                    mBackNavigationInfo.getCustomAnimationInfo();
-            if (animationInfo != null && mCustomizeActivityAnimation != null
-                    && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
-                mAnimationDefinition.get(type).resetWaitingAnimation();
-                mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                        mCustomizeActivityAnimation.mBackAnimationRunner);
-            }
-        }
-        return mAnimationDefinition.get(type);
-    }
 
     private void createAdapter() {
-        IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                    IBackAnimationFinishedCallback finishedCallback) {
-                mShellExecutor.execute(() -> {
-                    if (mBackNavigationInfo == null) {
-                        Log.e(TAG, "Lack of navigation info to start animation.");
-                        return;
-                    }
-                    final int type = mBackNavigationInfo.getType();
-                    final BackAnimationRunner runner = getAnimationRunnerAndInit();
-                    if (runner == null) {
-                        Log.e(TAG, "Animation didn't be defined for type "
-                                + BackNavigationInfo.typeToString(type));
-                        if (finishedCallback != null) {
-                            try {
-                                finishedCallback.onAnimationFinished(false);
-                            } catch (RemoteException e) {
-                                Log.w(TAG, "Failed call IBackNaviAnimationController", e);
-                            }
-                        }
-                        return;
-                    }
-                    mActiveCallback = runner.getCallback();
-                    mBackAnimationFinishedCallback = finishedCallback;
+        IBackAnimationRunner runner =
+                new IBackAnimationRunner.Stub() {
+                    @Override
+                    public void onAnimationStart(
+                            RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
+                            RemoteAnimationTarget[] nonApps,
+                            IBackAnimationFinishedCallback finishedCallback) {
+                        mShellExecutor.execute(
+                                () -> {
+                                    if (mBackNavigationInfo == null) {
+                                        Log.e(TAG, "Lack of navigation info to start animation.");
+                                        return;
+                                    }
+                                    final BackAnimationRunner runner =
+                                            mShellBackAnimationRegistry.getAnimationRunnerAndInit(
+                                                    mBackNavigationInfo);
+                                    if (runner == null) {
+                                        if (finishedCallback != null) {
+                                            try {
+                                                finishedCallback.onAnimationFinished(false);
+                                            } catch (RemoteException e) {
+                                                Log.w(
+                                                        TAG,
+                                                        "Failed call IBackNaviAnimationController",
+                                                        e);
+                                            }
+                                        }
+                                        return;
+                                    }
+                                    mActiveCallback = runner.getCallback();
+                                    mBackAnimationFinishedCallback = finishedCallback;
 
-                    ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
-                    runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
-                            BackAnimationController.this::onBackAnimationFinished));
+                                    ProtoLog.d(
+                                            WM_SHELL_BACK_PREVIEW,
+                                            "BackAnimationController: startAnimation()");
+                                    runner.startAnimation(
+                                            apps,
+                                            wallpapers,
+                                            nonApps,
+                                            () ->
+                                                    mShellExecutor.execute(
+                                                            BackAnimationController.this
+                                                                    ::onBackAnimationFinished));
 
-                    if (apps.length >= 1) {
-                        dispatchOnBackStarted(
-                                mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+                                    if (apps.length >= 1) {
+                                        dispatchOnBackStarted(
+                                                mActiveCallback,
+                                                mTouchTracker.createStartEvent(apps[0]));
+                                    }
+
+                                    // Dispatch the first progress after animation start for
+                                    // smoothing the initial animation, instead of waiting for next
+                                    // onMove.
+                                    final BackMotionEvent backFinish =
+                                            mTouchTracker.createProgressEvent();
+                                    dispatchOnBackProgressed(mActiveCallback, backFinish);
+                                    if (!mBackGestureStarted) {
+                                        // if the down -> up gesture happened before animation
+                                        // start, we have to trigger the uninterruptible transition
+                                        // to finish the back animation.
+                                        startPostCommitAnimation();
+                                    }
+                                });
                     }
 
-                    // Dispatch the first progress after animation start for smoothing the initial
-                    // animation, instead of waiting for next onMove.
-                    final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
-                    dispatchOnBackProgressed(mActiveCallback, backFinish);
-                    if (!mBackGestureStarted) {
-                        // if the down -> up gesture happened before animation start, we have to
-                        // trigger the uninterruptible transition to finish the back animation.
-                        startPostCommitAnimation();
+                    @Override
+                    public void onAnimationCancelled() {
+                        mShellExecutor.execute(
+                                () -> {
+                                    if (!mShellBackAnimationRegistry.cancel(
+                                            mBackNavigationInfo.getType())) {
+                                        return;
+                                    }
+                                    if (!mBackGestureStarted) {
+                                        invokeOrCancelBack();
+                                    }
+                                });
                     }
-                });
-            }
-
-            @Override
-            public void onAnimationCancelled() {
-                mShellExecutor.execute(() -> {
-                    final BackAnimationRunner runner = mAnimationDefinition.get(
-                            mBackNavigationInfo.getType());
-                    if (runner == null) {
-                        return;
-                    }
-                    runner.cancelAnimation();
-                    if (!mBackGestureStarted) {
-                        invokeOrCancelBack();
-                    }
-                });
-            }
-        };
+                };
         mBackAnimationAdapter = new BackAnimationAdapter(runner);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 913239f7..431df21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -32,7 +32,7 @@
  * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
  * trigger the real back behavior.
  */
-class BackAnimationRunner {
+public class BackAnimationRunner {
     private static final String TAG = "ShellBackPreview";
 
     private final IOnBackInvokedCallback mCallback;
@@ -44,8 +44,8 @@
     /** True when the back animation is cancelled */
     private boolean mAnimationCancelled;
 
-    BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
-            @NonNull IRemoteAnimationRunner runner) {
+    public BackAnimationRunner(
+            @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
         mCallback = callback;
         mRunner = runner;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e..114486e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -51,9 +51,11 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
+import javax.inject.Inject;
+
 /** Class that defines cross-activity animation. */
 @ShellMainThread
-class CrossActivityAnimation {
+public class CrossActivityAnimation extends ShellBackAnimation {
     /**
      * Minimum scale of the entering/closing window.
      */
@@ -106,6 +108,7 @@
     private final SpringAnimation mLeavingProgressSpring;
     // Max window x-shift in pixels.
     private final float mWindowXShift;
+    private final BackAnimationRunner mBackAnimationRunner;
 
     private float mEnteringProgress = 0f;
     private float mLeavingProgress = 0f;
@@ -126,11 +129,11 @@
     private IRemoteAnimationFinishedCallback mFinishCallback;
 
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
 
     private final BackAnimationBackground mBackground;
 
-    CrossActivityAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CrossActivityAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
         mBackground = background;
@@ -357,6 +360,11 @@
         mTransaction.apply();
     }
 
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
+    }
+
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
@@ -371,7 +379,15 @@
 
         @Override
         public void onBackCancelled() {
-            mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+            mProgressAnimator.onBackCancelled(() -> {
+                // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
+                // and if we release all animation leash first, the leavingProgressSpring won't
+                // able to update the animation anymore, which cause flicker.
+                // Here should force update the closing animation target to the final stage before
+                // release it.
+                setLeavingProgress(0);
+                finishAnimation();
+            });
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a7dd27a..209d853 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -47,21 +47,23 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
+import javax.inject.Inject;
+
 /**
  * Controls the animation of swiping back and returning to another task.
  *
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the closing and entering app windows.
- * Once the gesture is committed, the second part remains the closing window in place.
- * The entering window plays the rest of app opening transition to enter full screen.
+ * <p>This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows. Once the gesture is committed, the second
+ * part remains the closing window in place. The entering window plays the rest of app opening
+ * transition to enter full screen.
  *
- * This animation is used only for apps that enable back dispatching via
- * {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
- * navigation to launcher starts.
+ * <p>This animation is used only for apps that enable back dispatching via {@link
+ * android.window.OnBackInvokedDispatcher}. The controller registers an {@link
+ * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
+ * launcher starts.
  */
 @ShellMainThread
-class CrossTaskBackAnimation {
+public class CrossTaskBackAnimation extends ShellBackAnimation {
     private static final int BACKGROUNDCOLOR = 0x43433A;
 
     /**
@@ -104,28 +106,41 @@
 
     private final float[] mTmpFloat9 = new float[9];
     private final float[] mTmpTranslate = {0, 0, 0};
-
+    private final BackAnimationRunner mBackAnimationRunner;
+    private final BackAnimationBackground mBackground;
     private RemoteAnimationTarget mEnteringTarget;
     private RemoteAnimationTarget mClosingTarget;
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
     private boolean mBackInProgress = false;
-
     private boolean mIsRightEdge;
     private float mProgress = 0;
     private PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
     private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
 
-    private final BackAnimationBackground mBackground;
-
-    CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
         mBackground = background;
     }
 
+    private static void computeScaleTransformMatrix(float scale, float[] matrix) {
+        matrix[0] = scale;
+        matrix[1] = 0;
+        matrix[2] = 0;
+        matrix[3] = 0;
+        matrix[4] = scale;
+        matrix[5] = 0;
+        matrix[6] = 0;
+        matrix[7] = 0;
+        matrix[8] = scale;
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
     private float getInterpolatedProgress(float backProgress) {
         return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
     }
@@ -233,18 +248,6 @@
         mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
     }
 
-    static void computeScaleTransformMatrix(float scale, float[] matrix) {
-        matrix[0] = scale;
-        matrix[1] = 0;
-        matrix[2] = 0;
-        matrix[3] = 0;
-        matrix[4] = scale;
-        matrix[5] = 0;
-        matrix[6] = 0;
-        matrix[7] = 0;
-        matrix[8] = scale;
-    }
-
     private void finishAnimation() {
         if (mEnteringTarget != null) {
             mEnteringTarget.leash.release();
@@ -314,11 +317,12 @@
         valueAnimator.start();
     }
 
-    private static float mapRange(float value, float min, float max) {
-        return min + (value * (max - min));
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
     }
 
-    private final class Callback extends IOnBackInvokedCallback.Default  {
+    private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
             mProgressAnimator.onBackStarted(backEvent,
@@ -340,7 +344,7 @@
             mProgressAnimator.reset();
             onGestureCommitted();
         }
-    };
+    }
 
     private final class Runner extends IRemoteAnimationRunner.Default {
         @Override
@@ -360,5 +364,5 @@
             startBackAnimation();
             mFinishCallback = finishedCallback;
         }
-    };
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 2d6ec75..aca638c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -55,13 +55,13 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
-/**
- * Class that handle customized close activity transition animation.
- */
+import javax.inject.Inject;
+
+/** Class that handle customized close activity transition animation. */
 @ShellMainThread
-class CustomizeActivityAnimation {
+public class CustomizeActivityAnimation extends ShellBackAnimation {
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
+    private final BackAnimationRunner mBackAnimationRunner;
     private final float mCornerRadius;
     private final SurfaceControl.Transaction mTransaction;
     private final BackAnimationBackground mBackground;
@@ -88,7 +88,8 @@
 
     private final Choreographer mChoreographer;
 
-    CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
         this(context, background, new SurfaceControl.Transaction(), null);
     }
 
@@ -258,10 +259,12 @@
         valueAnimator.start();
     }
 
-    /**
-     * Load customize animation before animation start.
-     */
-    boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+    /** Load customize animation before animation start. */
+    @Override
+    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+        if (animationInfo == null) {
+            return false;
+        }
         final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
         if (result != null) {
             mCloseAnimation = result.mCloseAnimation;
@@ -272,6 +275,11 @@
         return false;
     }
 
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
+    }
+
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
new file mode 100644
index 0000000..312e88d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.back;
+
+import android.window.BackNavigationInfo;
+
+import javax.inject.Qualifier;
+
+/** Base class for all back animations. */
+public abstract class ShellBackAnimation {
+    @Qualifier
+    public @interface CrossActivity {}
+
+    @Qualifier
+    public @interface CrossTask {}
+
+    @Qualifier
+    public @interface CustomizeActivity {}
+
+    @Qualifier
+    public @interface ReturnToHome {}
+
+    /** Retrieve the {@link BackAnimationRunner} associated with this animation. */
+    public abstract BackAnimationRunner getRunner();
+
+    /**
+     * Prepare the next animation with customized animation.
+     *
+     * @return true if this type of back animation should override the default.
+     */
+    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+        return false;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
new file mode 100644
index 0000000..62b18f3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.back;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.window.BackNavigationInfo;
+
+/** Registry for all types of default back animations */
+public class ShellBackAnimationRegistry {
+    private static final String TAG = "ShellBackPreview";
+
+    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+    private final ShellBackAnimation mDefaultCrossActivityAnimation;
+    private final ShellBackAnimation mCustomizeActivityAnimation;
+
+    public ShellBackAnimationRegistry(
+            @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
+            @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation,
+            @ShellBackAnimation.CustomizeActivity @Nullable
+                    ShellBackAnimation customizeActivityAnimation,
+            @ShellBackAnimation.ReturnToHome @Nullable
+                    ShellBackAnimation defaultBackToHomeAnimation) {
+        if (crossActivityAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner());
+        }
+        if (crossActivityAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner());
+        }
+        if (defaultBackToHomeAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner());
+        }
+
+        mDefaultCrossActivityAnimation = crossActivityAnimation;
+        mCustomizeActivityAnimation = customizeActivityAnimation;
+
+        // TODO(b/236760237): register dialog close animation when it's completed.
+    }
+
+    void registerAnimation(
+            @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
+        mAnimationDefinition.set(type, runner);
+    }
+
+    void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+        mAnimationDefinition.remove(type);
+    }
+
+    /**
+     * Start the {@link BackAnimationRunner} associated with a back target type.
+     *
+     * @param type back target type
+     * @return true if the animation is started, false if animation is not found for that type.
+     */
+    boolean startGesture(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        runner.startGesture();
+        return true;
+    }
+
+    /**
+     * Cancel the {@link BackAnimationRunner} associated with a back target type.
+     *
+     * @param type back target type
+     * @return true if the animation is started, false if animation is not found for that type.
+     */
+    boolean cancel(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        runner.cancelAnimation();
+        return true;
+    }
+
+    boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return true;
+        }
+        return runner.isAnimationCancelled();
+    }
+
+    boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        return runner.isWaitingAnimation();
+    }
+
+    void resetDefaultCrossActivity() {
+        if (mDefaultCrossActivityAnimation == null
+                || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+            return;
+        }
+        mAnimationDefinition.set(
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
+    }
+
+    BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
+        int type = backNavigationInfo.getType();
+        // Initiate customized cross-activity animation, or fall back to cross activity animation
+        if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+            if (mCustomizeActivityAnimation != null
+                    && mCustomizeActivityAnimation.prepareNextAnimation(
+                            backNavigationInfo.getCustomAnimationInfo())) {
+                mAnimationDefinition.get(type).resetWaitingAnimation();
+                mAnimationDefinition.set(
+                        BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                        mCustomizeActivityAnimation.getRunner());
+            }
+        }
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            Log.e(
+                    TAG,
+                    "Animation didn't be defined for type "
+                            + BackNavigationInfo.typeToString(type));
+        }
+        return runner;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 66b9ade6..8400dde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -37,6 +36,7 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
 import android.annotation.BinderThread;
@@ -85,6 +85,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
@@ -1008,9 +1009,7 @@
     }
 
     private void onNotificationPanelExpandedChanged(boolean expanded) {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
         if (mStackView != null && mStackView.isExpanded()) {
             if (expanded) {
                 mStackView.stopMonitoringSwipeUpGesture();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dce6b56..250e010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -47,7 +47,6 @@
     static final boolean DEBUG_USER_EDUCATION = false;
     static final boolean DEBUG_POSITIONER = false;
     public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
-    static final boolean DEBUG_BUBBLE_GESTURE = false;
     public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
 
     private static final boolean FORCE_SHOW_USER_EDUCATION = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f58b121..da5974f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,11 +21,11 @@
 
 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -75,6 +75,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
@@ -2024,9 +2025,7 @@
      * Monitor for swipe up gesture that is used to collapse expanded view
      */
     void startMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "startMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
 
         if (isGestureNavEnabled()) {
@@ -2046,9 +2045,7 @@
      * Stop monitoring for swipe up gesture
      */
     void stopMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stopMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 3a3a378..1375684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -18,10 +18,10 @@
 
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.hardware.input.InputManager;
-import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
@@ -29,6 +29,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
 
 /**
@@ -58,9 +59,7 @@
      * @param listener listener that is notified of touch events
      */
     void start(MotionEventListener listener) {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "start monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture");
 
         stopInternal();
 
@@ -76,9 +75,7 @@
     }
 
     void stop() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stop monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture");
         stopInternal();
     }
 
@@ -94,9 +91,7 @@
     }
 
     private void onInterceptTouch() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "intercept touch event");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event");
         if (mInputMonitor != null) {
             mInputMonitor.pilferPointers();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index 844526c..b7107f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -16,19 +16,20 @@
 
 package com.android.wm.shell.bubbles;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 /**
  * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
  */
@@ -112,10 +113,8 @@
     private boolean isInGestureRegion(MotionEvent ev) {
         // Only handles touch events beginning in navigation bar system gesture zone
         if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
-            if (DEBUG_BUBBLE_GESTURE) {
-                Log.d(TAG, "handling touch y=" + ev.getY()
-                        + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
+                    (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
             return true;
         }
         return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index e95e8e5..1b41f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -41,9 +41,9 @@
     private val ANIMATE_DURATION: Long = 200
 
     private val positioner: BubblePositioner = positioner
-    private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
-    private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
-    private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
+    private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
+    private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
+    private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
 
     private var isHiding = false
     private var realManageButtonRect = Rect()
@@ -122,7 +122,7 @@
             manageButton
                 .setOnClickListener {
                     hide()
-                    expandedView.findViewById<View>(R.id.manage_button).performClick()
+                    expandedView.requireViewById<View>(R.id.manage_button).performClick()
                 }
             gotItButton.setOnClickListener { hide() }
             setOnClickListener { hide() }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index d0598cd..5e3a077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -48,9 +48,9 @@
     private val positioner: BubblePositioner = positioner
     private val controller: BubbleController = controller
 
-    private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
-    private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
-    private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+    private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
+    private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
+    private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
 
     var isHiding = false
         private set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
new file mode 100644
index 0000000..a141ff9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.pip
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.pip.PipUtils
+
+class PipAppOpsListener(
+    private val mContext: Context,
+    private val mCallback: Callback,
+    private val mMainExecutor: ShellExecutor
+) {
+    private val mAppOpsManager: AppOpsManager = checkNotNull(
+        mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+    private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
+        try {
+            // Dismiss the PiP once the user disables the app ops setting for that package
+            val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+            val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
+            val userId = topPipActivityInfo.second
+            val appInfo = mContext.packageManager
+                .getApplicationInfoAsUser(packageName, 0, userId)
+            if (appInfo.packageName == componentName.packageName &&
+                mAppOpsManager.checkOpNoThrow(
+                    AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
+                    packageName
+                ) != AppOpsManager.MODE_ALLOWED
+            ) {
+                mMainExecutor.execute { mCallback.dismissPip() }
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            // Unregister the listener if the package can't be found
+            unregisterAppOpsListener()
+        }
+    }
+
+    fun onActivityPinned(packageName: String) {
+        // Register for changes to the app ops setting for this package while it is in PiP
+        registerAppOpsListener(packageName)
+    }
+
+    fun onActivityUnpinned() {
+        // Unregister for changes to the previously PiP'ed package
+        unregisterAppOpsListener()
+    }
+
+    private fun registerAppOpsListener(packageName: String) {
+        mAppOpsManager.startWatchingMode(
+            AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
+            mAppOpsChangedListener
+        )
+    }
+
+    private fun unregisterAppOpsListener() {
+        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
+    }
+
+    /** Callback for PipAppOpsListener to request changes to the PIP window.  */
+    interface Callback {
+        /** Dismisses the PIP window.  */
+        fun dismissPip()
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
similarity index 98%
rename from core/java/com/android/internal/policy/DividerSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index a065e2b..1901e0b 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open 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.internal.policy;
+package com.android.wm.shell.common.split;
 
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2dbc444..0b0c693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -53,7 +53,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
similarity index 97%
rename from core/java/com/android/internal/policy/DockedDividerUtils.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
index b61b9de..f25dfea 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open 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.internal.policy;
+package com.android.wm.shell.common.split;
 
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index d3fada3..5d7e532 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -25,8 +25,8 @@
 import static android.view.WindowManager.DOCKED_TOP;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +58,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 9facbd5..b52a118 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -49,11 +49,11 @@
 import java.util.Optional;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link com.android.systemui.dagger.WMComponent}).
  *
- * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 422e3b0..c06b22c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.back.BackAnimationBackground;
 import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DevicePostureController;
@@ -44,6 +45,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
+import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -73,7 +75,6 @@
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -107,9 +108,9 @@
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}).
  *
- * This module only defines *common* dependencies across various SystemUI implementations,
+ * <p>This module only defines *common* dependencies across various SystemUI implementations,
  * dependencies that are device/form factor SystemUI implementation specific should go into their
  * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
@@ -122,6 +123,12 @@
 
     @WMSingleton
     @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    @WMSingleton
+    @Provides
     static DisplayController provideDisplayController(Context context,
             IWindowManager wmService,
             ShellInit shellInit,
@@ -303,16 +310,25 @@
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread Handler backgroundHandler,
-            BackAnimationBackground backAnimationBackground
-    ) {
+            BackAnimationBackground backAnimationBackground,
+            Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
         if (BackAnimationController.IS_ENABLED) {
-            return Optional.of(
-                    new BackAnimationController(shellInit, shellController, shellExecutor,
-                            backgroundHandler, context, backAnimationBackground));
+            return shellBackAnimationRegistry.map(
+                    (animations) ->
+                            new BackAnimationController(
+                                    shellInit,
+                                    shellController,
+                                    shellExecutor,
+                                    backgroundHandler,
+                                    context,
+                                    backAnimationBackground,
+                                    animations));
         }
         return Optional.empty();
     }
 
+    @BindsOptionalOf
+    abstract ShellBackAnimationRegistry optionalBackAnimationRegistry();
 
     //
     // Bubbles (optional feature)
@@ -797,7 +813,6 @@
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 881c8f5..36d2a70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -100,17 +101,19 @@
 import java.util.Optional;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link WMComponent}).
  *
- * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
-@Module(includes = {
-        WMShellBaseModule.class,
-        PipModule.class
-})
+@Module(
+        includes = {
+            WMShellBaseModule.class,
+            PipModule.class,
+            ShellBackAnimationModule.class,
+        })
 public abstract class WMShellModule {
 
     //
@@ -325,12 +328,13 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
                 transactionPool, iconProvider, recentTasks, launchAdjacentController,
-                windowDecorViewModel, mainExecutor);
+                windowDecorViewModel, desktopTasksController, mainExecutor);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
new file mode 100644
index 0000000..b34c6b2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dagger.back;
+
+import com.android.wm.shell.back.CrossActivityAnimation;
+import com.android.wm.shell.back.CrossTaskBackAnimation;
+import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.ShellBackAnimation;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/** Default animation definitions for predictive back. */
+@Module
+public interface ShellBackAnimationModule {
+    /** Default animation registry */
+    @Provides
+    static ShellBackAnimationRegistry provideBackAnimationRegistry(
+            @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity,
+            @ShellBackAnimation.CrossTask ShellBackAnimation crossTask,
+            @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) {
+        return new ShellBackAnimationRegistry(
+                crossActivity,
+                crossTask,
+                customizeActivity,
+                /* defaultBackToHomeAnimation= */ null);
+    }
+
+    /** Default cross activity back animation */
+    @Binds
+    @ShellBackAnimation.CrossActivity
+    ShellBackAnimation bindCrossActivityShellBackAnimation(
+            CrossActivityAnimation crossActivityAnimation);
+
+    /** Default cross task back animation */
+    @Binds
+    @ShellBackAnimation.CrossTask
+    ShellBackAnimation provideCrossTaskShellBackAnimation(
+            CrossTaskBackAnimation crossTaskBackAnimation);
+
+    /** Default customized activity back animation */
+    @Binds
+    @ShellBackAnimation.CustomizeActivity
+    ShellBackAnimation provideCustomizeActivityShellBackAnimation(
+            CustomizeActivityAnimation customizeActivityAnimation);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 54be901..9bf973f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -31,13 +31,13 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index f29b3a3..e8fae24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.PipMediaController;
@@ -37,12 +36,6 @@
  */
 @Module
 public abstract class Pip1SharedModule {
-    @WMSingleton
-    @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
-    }
-
     // Needs handler for registering broadcast receivers
     @WMSingleton
     @Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 52c6d20..80ffbb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -29,12 +29,12 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index db6c258..5b24d7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -535,6 +535,11 @@
         }
 
         @Override
+        public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+        }
+
+        @Override
         public void stashDesktopApps(int displayId) throws RemoteException {
             // Stashing of desktop apps not needed. Apps always launch on desktop
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b15fd91..1d46e75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
@@ -33,7 +34,6 @@
 import android.os.SystemProperties
 import android.util.DisplayMetrics.DENSITY_DEFAULT
 import android.view.SurfaceControl
-import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
@@ -105,6 +106,9 @@
         get() = context.resources.getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
 
+    // This is public to avoid cyclic dependency; it is set by SplitScreenController
+    lateinit var splitScreenController: SplitScreenController
+
     init {
         desktopMode = DesktopModeImpl()
         if (DesktopModeStatus.isProto2Enabled()) {
@@ -262,6 +266,19 @@
         }
     }
 
+    /**
+     * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+     * here instead of initial wct to both avoid flicker and to have task bounds to use for
+     * the staging animation.
+     *
+     * @param taskInfo task entering split that requires a bounds update
+     */
+    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+        val wct = WindowContainerTransaction()
+        wct.setBounds(taskInfo.token, Rect())
+        shellTaskOrganizer.applyTransaction(wct)
+    }
+
     /** Move a task with given `taskId` to fullscreen */
     fun moveToFullscreen(taskId: Int) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -296,7 +313,7 @@
             task.taskId
         )
         val wct = WindowContainerTransaction()
-        wct.setBounds(task.token, null)
+        wct.setBounds(task.token, Rect())
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
@@ -533,6 +550,7 @@
         )
         // Check if we should skip handling this transition
         var reason = ""
+        val triggerTask = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
@@ -541,19 +559,19 @@
                     false
                 }
                 // Only handle when it is a task transition
-                request.triggerTask == null -> {
+                triggerTask == null -> {
                     reason = "triggerTask is null"
                     false
                 }
                 // Only handle standard type tasks
-                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
-                    reason = "activityType not handled (${request.triggerTask.activityType})"
+                triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+                    reason = "activityType not handled (${triggerTask.activityType})"
                     false
                 }
                 // Only handle fullscreen or freeform tasks
-                request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
-                    reason = "windowingMode not handled (${request.triggerTask.windowingMode})"
+                triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                        triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+                    reason = "windowingMode not handled (${triggerTask.windowingMode})"
                     false
                 }
                 // Otherwise process it
@@ -569,17 +587,17 @@
             return null
         }
 
-        val task: RunningTaskInfo = request.triggerTask
-
-        val result = when {
-            // If display has tasks stashed, handle as stashed launch
-            desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
-            // Check if fullscreen task should be updated
-            task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
-            // Check if freeform task should be updated
-            task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
-            else -> {
-                null
+        val result = triggerTask?.let { task ->
+            when {
+                // If display has tasks stashed, handle as stashed launch
+                desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+                // Check if fullscreen task should be updated
+                task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+                // Check if freeform task should be updated
+                task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+                else -> {
+                    null
+                }
             }
         }
         KtProtoLog.v(
@@ -686,13 +704,43 @@
             WINDOWING_MODE_FULLSCREEN
         }
         wct.setWindowingMode(taskInfo.token, targetWindowingMode)
-        wct.setBounds(taskInfo.token, null)
+        wct.setBounds(taskInfo.token, Rect())
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
         }
     }
 
-    private fun getFullscreenDensityDpi(): Int {
+    /**
+     * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+     * animation; see {@link onDesktopSplitSelectAnimComplete}
+     */
+    private fun addMoveToSplitChanges(
+        wct: WindowContainerTransaction,
+        taskInfo: RunningTaskInfo
+    ) {
+        wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+        // The task's density may have been overridden in freeform; revert it here as we don't
+        // want it overridden in multi-window.
+        wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+    }
+
+    /**
+     * Requests a task be transitioned from desktop to split select. Applies needed windowing
+     * changes if this transition is enabled.
+     */
+    fun requestSplit(
+        taskInfo: RunningTaskInfo
+    ) {
+        val windowingMode = taskInfo.windowingMode
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+        ) {
+            val wct = WindowContainerTransaction()
+            addMoveToSplitChanges(wct, taskInfo)
+            splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+        }
+    }
+
+    private fun getDefaultDensityDpi(): Int {
         return context.resources.displayMetrics.densityDpi
     }
 
@@ -969,6 +1017,13 @@
             return result[0]
         }
 
+        override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                controller,
+                "onDesktopSplitSelectAnimComplete"
+            ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+        }
+
         override fun setTaskListener(listener: IDesktopTaskListener?) {
             KtProtoLog.v(
                     WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index ee3a080..47edfd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 
 /**
@@ -38,6 +39,9 @@
     /** Get count of visible desktop tasks on the given display */
     int getVisibleTaskCount(int displayId);
 
+    /** Perform cleanup transactions after the animation to split select is complete */
+    oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
     /** Set listener that will receive callbacks about updates to desktop tasks */
     oneway void setTaskListener(IDesktopTaskListener listener);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index b9cb5c7..9debb25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -69,7 +69,7 @@
     ): Boolean {
         val change = findRelevantChange(info)
         val leash = change.leash
-        val taskId = change.taskInfo.taskId
+        val taskId = checkNotNull(change.taskInfo).taskId
         val startBounds = change.startAbsBounds
         val endBounds = change.endAbsBounds
         val windowDecor =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
deleted file mode 100644
index 48a3fc2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
+++ /dev/null
@@ -1,94 +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.wm.shell.pip;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-public class PipAppOpsListener {
-    private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
-    private Context mContext;
-    private ShellExecutor mMainExecutor;
-    private AppOpsManager mAppOpsManager;
-    private Callback mCallback;
-
-    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            try {
-                // Dismiss the PiP once the user disables the app ops setting for that package
-                final Pair<ComponentName, Integer> topPipActivityInfo =
-                        PipUtils.getTopPipActivity(mContext);
-                if (topPipActivityInfo.first != null) {
-                    final ApplicationInfo appInfo = mContext.getPackageManager()
-                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
-                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
-                            mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
-                                    packageName) != MODE_ALLOWED) {
-                        mMainExecutor.execute(() -> mCallback.dismissPip());
-                    }
-                }
-            } catch (NameNotFoundException e) {
-                // Unregister the listener if the package can't be found
-                unregisterAppOpsListener();
-            }
-        }
-    };
-
-    public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
-        mContext = context;
-        mMainExecutor = mainExecutor;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mCallback = callback;
-    }
-
-    public void onActivityPinned(String packageName) {
-        // Register for changes to the app ops setting for this package while it is in PiP
-        registerAppOpsListener(packageName);
-    }
-
-    public void onActivityUnpinned() {
-        // Unregister for changes to the previously PiP'ed package
-        unregisterAppOpsListener();
-    }
-
-    private void registerAppOpsListener(String packageName) {
-        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
-                mAppOpsChangedListener);
-    }
-
-    private void unregisterAppOpsListener() {
-        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
-    }
-
-    /** Callback for PipAppOpsListener to request changes to the PIP window. */
-    public interface Callback {
-        /** Dismisses the PIP window. */
-        void dismissPip();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index f396f3f..b872267 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -75,6 +75,7 @@
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.IPip;
@@ -82,7 +83,6 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
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 43d3f36..b251f6f 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
@@ -41,7 +41,7 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 2482acf..e3544c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -47,10 +47,10 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b2a189b..ee55211 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -62,13 +62,16 @@
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
     private TvPipBackgroundView mPipBackgroundView;
-    private boolean mMenuIsFocused;
 
     @TvPipMenuMode
     private int mCurrentMenuMode = MODE_NO_MENU;
     @TvPipMenuMode
     private int mPrevMenuMode = MODE_NO_MENU;
 
+    /** When the window gains focus, enter this menu mode */
+    @TvPipMenuMode
+    private int mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+
     @IntDef(prefix = { "MODE_" }, value = {
         MODE_NO_MENU,
         MODE_MOVE_MENU,
@@ -170,6 +173,9 @@
         mPipMenuView = createTvPipMenuView();
         setUpViewSurfaceZOrder(mPipMenuView, 1);
         addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+        mPipMenuView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
+            onPipWindowFocusChanged(hasFocus);
+        });
     }
 
     @VisibleForTesting
@@ -224,13 +230,14 @@
     void showMovementMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementMenu()", TAG);
-        switchToMenuMode(MODE_MOVE_MENU);
+        requestMenuMode(MODE_MOVE_MENU);
     }
 
     @Override
     public void showMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
-        switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
+        mPipMenuView.resetMenu();
+        requestMenuMode(MODE_ALL_ACTIONS_MENU);
     }
 
     void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -250,7 +257,7 @@
     void closeMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: closeMenu()", TAG);
-        switchToMenuMode(MODE_NO_MENU);
+        requestMenuMode(MODE_NO_MENU);
     }
 
     @Override
@@ -392,11 +399,15 @@
         }
     }
 
-    // Start methods handling {@link TvPipMenuMode}
+    // Beginning of convenience methods for {@link TvPipMenuMode}
 
     @VisibleForTesting
     boolean isMenuOpen() {
-        return mCurrentMenuMode != MODE_NO_MENU;
+        return isMenuOpen(mCurrentMenuMode);
+    }
+
+    private static boolean isMenuOpen(@TvPipMenuMode int menuMode) {
+        return menuMode != MODE_NO_MENU;
     }
 
     @VisibleForTesting
@@ -409,46 +420,6 @@
         return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
     }
 
-    private void switchToMenuMode(@TvPipMenuMode int menuMode) {
-        switchToMenuMode(menuMode, false);
-    }
-
-    private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
-        // Note: we intentionally don't return early here, because the TvPipMenuView needs to
-        // refresh the Ui even if there is no menu mode change.
-        mPrevMenuMode = mCurrentMenuMode;
-        mCurrentMenuMode = menuMode;
-
-        ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
-                getMenuModeString(), getMenuModeString(mPrevMenuMode));
-
-        updateUiOnNewMenuModeRequest(resetMenu);
-        updateDelegateOnNewMenuModeRequest();
-    }
-
-    private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
-        if (mPipMenuView == null || mPipBackgroundView == null) return;
-
-        mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
-        mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
-        mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
-        grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
-    }
-
-    private void updateDelegateOnNewMenuModeRequest() {
-        if (mPrevMenuMode == mCurrentMenuMode) return;
-        if (mDelegate == null) return;
-
-        if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
-            mDelegate.onInMoveModeChanged();
-        }
-
-        if (mCurrentMenuMode == MODE_NO_MENU) {
-            mDelegate.onMenuClosed();
-        }
-    }
-
     @VisibleForTesting
     String getMenuModeString() {
         return getMenuModeString(mCurrentMenuMode);
@@ -467,6 +438,90 @@
         }
     }
 
+    // Beginning of methods handling switching between menu modes
+
+    private void requestMenuMode(@TvPipMenuMode int menuMode) {
+        if (isMenuOpen() == isMenuOpen(menuMode)) {
+            // No need to request a focus change. We can directly switch to the new mode.
+            switchToMenuMode(menuMode);
+        } else {
+            if (isMenuOpen(menuMode)) {
+                mMenuModeOnFocus = menuMode;
+            }
+
+            // Send a request to gain window focus if the menu is open, or lose window focus
+            // otherwise. Once the focus change happens, we will request the new mode in the
+            // callback {@link #onPipWindowFocusChanged}.
+            requestPipMenuFocus(isMenuOpen(menuMode));
+        }
+        // Note: we don't handle cases where there is a focus change currently in flight, because
+        // this is very unlikely to happen in practice and would complicate the logic.
+    }
+
+    private void requestPipMenuFocus(boolean focus) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: requestPipMenuFocus(%b)", TAG, focus);
+
+        try {
+            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+                    mSystemWindows.getFocusGrantToken(mPipMenuView), focus);
+        } catch (Exception e) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Unable to update focus, %s", TAG, e);
+        }
+    }
+
+    /**
+     * Called when the menu window gains or loses focus.
+     */
+    @VisibleForTesting
+    void onPipWindowFocusChanged(boolean focused) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+        switchToMenuMode(focused ? mMenuModeOnFocus : MODE_NO_MENU);
+
+        // Reset the default menu mode for focused state.
+        mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+    }
+
+    /**
+     * Immediately switches to the menu mode in the given request. Updates the mDelegate and the UI.
+     * Doesn't handle any focus changes.
+     */
+    private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: switchToMenuMode: from=%s, to=%s", TAG, getMenuModeString(),
+                getMenuModeString(menuMode));
+
+        if (mCurrentMenuMode == menuMode) return;
+
+        mPrevMenuMode = mCurrentMenuMode;
+        mCurrentMenuMode = menuMode;
+        updateUiOnNewMenuModeRequest();
+        updateDelegateOnNewMenuModeRequest();
+    }
+
+    private void updateUiOnNewMenuModeRequest() {
+        if (mPipMenuView == null || mPipBackgroundView == null) return;
+
+        mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+        mPipMenuView.transitionToMenuMode(mCurrentMenuMode);
+        mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
+    }
+
+    private void updateDelegateOnNewMenuModeRequest() {
+        if (mPrevMenuMode == mCurrentMenuMode) return;
+        if (mDelegate == null) return;
+
+        if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+            mDelegate.onInMoveModeChanged();
+        }
+
+        if (!isMenuOpen()) {
+            mDelegate.onMenuClosed();
+        }
+    }
+
     // Start {@link TvPipMenuView.Delegate} methods
 
     @Override
@@ -476,42 +531,19 @@
     }
 
     @Override
-    public void onBackPress() {
-        if (!onExitMoveMode()) {
-            closeMenu();
-        }
-    }
-
-    @Override
-    public boolean onExitMoveMode() {
+    public void onExitCurrentMenuMode() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
-
-        final int saveMenuMode = mCurrentMenuMode;
-        if (isInMoveMode()) {
-            switchToMenuMode(mPrevMenuMode);
-        }
-        return saveMenuMode == MODE_MOVE_MENU;
+                "%s: onExitCurrentMenuMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+        requestMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU);
     }
 
     @Override
-    public boolean onPipMovement(int keycode) {
+    public void onPipMovement(int keycode) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
         if (isInMoveMode()) {
             mDelegate.movePip(keycode);
         }
-        return isInMoveMode();
-    }
-
-    @Override
-    public void onPipWindowFocusChanged(boolean focused) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
-        mMenuIsFocused = focused;
-        if (!focused && isMenuOpen()) {
-            closeMenu();
-        }
     }
 
     interface Delegate {
@@ -524,21 +556,6 @@
         void closeEduText();
     }
 
-    private void grantPipMenuFocus(boolean grantFocus) {
-        if (mMenuIsFocused == grantFocus) return;
-
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: grantWindowFocus(%b)", TAG, grantFocus);
-
-        try {
-            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
-                    mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
-        } catch (Exception e) {
-            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Unable to update focus, %s", TAG, e);
-        }
-    }
-
     private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
         private final View mView;
         private final int mZOrder;
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 613791c..7c15637 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
@@ -328,7 +328,7 @@
         return menuUiBounds;
     }
 
-    void transitionToMenuMode(int menuMode, boolean resetMenu) {
+    void transitionToMenuMode(int menuMode) {
         switch (menuMode) {
             case MODE_NO_MENU:
                 hideAllUserControls();
@@ -337,7 +337,7 @@
                 showMoveMenu();
                 break;
             case MODE_ALL_ACTIONS_MENU:
-                showAllActionsMenu(resetMenu);
+                showAllActionsMenu();
                 break;
             default:
                 throw new IllegalArgumentException(
@@ -362,13 +362,13 @@
         mEduTextDrawer.closeIfNeeded();
     }
 
-    private void showAllActionsMenu(boolean resetMenu) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+    void resetMenu() {
+        scrollToFirstAction();
+    }
 
-        if (resetMenu) {
-            scrollToFirstAction();
-        }
+    private void showAllActionsMenu() {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: showAllActionsMenu()", TAG);
 
         if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
 
@@ -431,12 +431,6 @@
         }
     }
 
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        super.onWindowFocusChanged(hasWindowFocus);
-        mListener.onPipWindowFocusChanged(hasWindowFocus);
-    }
-
     private void animateAlphaTo(float alpha, View view) {
         if (view.getAlpha() == alpha) {
             return;
@@ -483,28 +477,28 @@
         if (event.getAction() == ACTION_UP) {
 
             if (event.getKeyCode() == KEYCODE_BACK) {
-                mListener.onBackPress();
+                mListener.onExitCurrentMenuMode();
                 return true;
             }
 
-            if (mA11yManager.isEnabled()) {
-                return super.dispatchKeyEvent(event);
-            }
-
-            switch (event.getKeyCode()) {
-                case KEYCODE_DPAD_UP:
-                case KEYCODE_DPAD_DOWN:
-                case KEYCODE_DPAD_LEFT:
-                case KEYCODE_DPAD_RIGHT:
-                    return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
-                            event);
-                case KEYCODE_ENTER:
-                case KEYCODE_DPAD_CENTER:
-                    return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
-                default:
-                    break;
+            if (mCurrentMenuMode == MODE_MOVE_MENU && !mA11yManager.isEnabled()) {
+                switch (event.getKeyCode()) {
+                    case KEYCODE_DPAD_UP:
+                    case KEYCODE_DPAD_DOWN:
+                    case KEYCODE_DPAD_LEFT:
+                    case KEYCODE_DPAD_RIGHT:
+                        mListener.onPipMovement(event.getKeyCode());
+                        return true;
+                    case KEYCODE_ENTER:
+                    case KEYCODE_DPAD_CENTER:
+                        mListener.onExitCurrentMenuMode();
+                        return true;
+                    default:
+                        // Dispatch key event as normal below
+                }
             }
         }
+
         return super.dispatchKeyEvent(event);
     }
 
@@ -529,7 +523,7 @@
         if (a11yEnabled) {
             mA11yDoneButton.setVisibility(VISIBLE);
             mA11yDoneButton.setOnClickListener(v -> {
-                mListener.onExitMoveMode();
+                mListener.onExitCurrentMenuMode();
             });
             mA11yDoneButton.requestFocus();
             mA11yDoneButton.requestAccessibilityFocus();
@@ -626,26 +620,15 @@
 
     interface Listener {
 
-        void onBackPress();
+        /**
+         * Called when a button for exiting the current menu mode was pressed.
+         */
+        void onExitCurrentMenuMode();
 
         /**
-         * Called when a button for exiting move mode was pressed.
-         *
-         * @return true if the event was handled or false if the key event should be handled by the
-         * next receiver.
+         * Called when a button to move the PiP in a certain direction, indicated by keycode.
          */
-        boolean onExitMoveMode();
-
-        /**
-         * @return whether pip movement was handled.
-         */
-        boolean onPipMovement(int keycode);
-
-        /**
-         * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
-         * has lost focus.
-         */
-        void onPipWindowFocusChanged(boolean focused);
+        void onPipMovement(int keycode);
 
         /**
          *  The edu text closing impacts the size of the Picture-in-Picture window and influences
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 3af1b75..05e4af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -55,6 +55,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            "Bubbles"),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index c414e70..14304a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -27,6 +27,7 @@
 import android.window.RemoteTransition;
 
 import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
 /**
  * Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@
     oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
 
     /**
+     * Registers a split select listener.
+     */
+    oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+    /**
+     * Unregisters a split select listener.
+     */
+    oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+    /**
      * Removes a task from the side stage.
      */
     oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
 }
-// Last id = 19
\ No newline at end of file
+// Last id = 21
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
new file mode 100644
index 0000000..7171da5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
+ */
+interface ISplitSelectListener {
+    /**
+     * Called when a task requests to enter split select
+     */
+    boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77..f20fe0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.graphics.Rect;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,13 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
+    /** Callback interface for listening to requests to enter split select */
+    interface SplitSelectListener {
+        default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+            return false;
+        }
+    }
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 5fa2654..210bf68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -87,6 +87,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -104,6 +105,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Class manages split-screen multitasking mode and implements the main interface
@@ -177,6 +179,7 @@
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final LaunchAdjacentController mLaunchAdjacentController;
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+    private final Optional<DesktopTasksController> mDesktopTasksController;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
     private final String[] mAppsSupportMultiInstances;
 
@@ -205,6 +208,7 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             ShellExecutor mainExecutor) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -223,6 +227,7 @@
         mRecentTasksOptional = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
+        mDesktopTasksController = desktopTasksController;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -254,6 +259,7 @@
             RecentTasksController recentTasks,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel,
+            DesktopTasksController desktopTasksController,
             ShellExecutor mainExecutor,
             StageCoordinator stageCoordinator) {
         mShellCommandHandler = shellCommandHandler;
@@ -273,6 +279,7 @@
         mRecentTasksOptional = Optional.of(recentTasks);
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+        mDesktopTasksController = Optional.of(desktopTasksController);
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
@@ -306,6 +313,7 @@
         }
         mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+        mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
     }
 
     protected StageCoordinator createStageCoordinator() {
@@ -468,6 +476,16 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    /** Register a split select listener */
+    public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.registerSplitSelectListener(listener);
+    }
+
+    /** Unregister a split select listener */
+    public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.unregisterSplitSelectListener(listener);
+    }
+
     public void goToFullscreenFromSplit() {
         mStageCoordinator.goToFullscreenFromSplit();
     }
@@ -485,6 +503,16 @@
         return mStageCoordinator.getActivateSplitPosition(taskInfo);
     }
 
+    /**
+     * Move a task to split select
+     * @param taskInfo the task being moved to split select
+     * @param wct transaction to apply if this is a valid request
+     */
+    public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -1088,6 +1116,8 @@
         private SplitScreenController mController;
         private final SingleInstanceRemoteListener<SplitScreenController,
                 ISplitScreenListener> mListener;
+        private final SingleInstanceRemoteListener<SplitScreenController,
+                ISplitSelectListener> mSelectListener;
         private final SplitScreen.SplitScreenListener mSplitScreenListener =
                 new SplitScreen.SplitScreenListener() {
                     @Override
@@ -1101,11 +1131,25 @@
                     }
                 };
 
+        private final SplitScreen.SplitSelectListener mSplitSelectListener =
+                new SplitScreen.SplitSelectListener() {
+                    @Override
+                    public boolean onRequestEnterSplitSelect(
+                            ActivityManager.RunningTaskInfo taskInfo) {
+                        AtomicBoolean result = new AtomicBoolean(false);
+                        mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+                        return result.get();
+                    }
+                };
+
         public ISplitScreenImpl(SplitScreenController controller) {
             mController = controller;
             mListener = new SingleInstanceRemoteListener<>(controller,
                     c -> c.registerSplitScreenListener(mSplitScreenListener),
                     c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+            mSelectListener = new SingleInstanceRemoteListener<>(controller,
+                    c -> c.registerSplitSelectListener(mSplitSelectListener),
+                    c -> c.unregisterSplitSelectListener(mSplitSelectListener));
         }
 
         /**
@@ -1131,6 +1175,18 @@
         }
 
         @Override
+        public void registerSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+                    (controller) -> mSelectListener.register(listener));
+        }
+
+        @Override
+        public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+                    (controller) -> mSelectListener.unregister());
+        }
+
+        @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
                     (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 99be5b8..7dec12a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -324,8 +324,10 @@
 
     void startFullscreenTransition(WindowContainerTransaction wct,
             @Nullable RemoteTransition handler) {
-        mTransitions.startTransition(TRANSIT_OPEN, wct,
-                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+        OneShotRemoteHandler fullscreenHandler =
+                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+        fullscreenHandler.setTransition(mTransitions
+                .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
     }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3758b68..6970068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -144,8 +144,10 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -185,6 +187,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
@@ -462,6 +465,15 @@
         return mLogger;
     }
 
+    void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        boolean enteredSplitSelect = false;
+        for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+            enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+        }
+        if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+    }
+
     void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             Bundle options, UserHandle user) {
         final boolean isEnteringSplit = !isSplitActive();
@@ -1657,6 +1669,14 @@
         mListeners.remove(listener);
     }
 
+    void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.add(listener);
+    }
+
+    void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.remove(listener);
+    }
+
     void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
         listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
         listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index a2301b1..c101425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -87,7 +87,7 @@
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, dragAndDropController, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
-                mainExecutor);
+                Optional.empty(), mainExecutor);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 84dcd4d..0c6adc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -376,8 +376,8 @@
     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
         final DrawableColorTester themeBGTester = new DrawableColorTester(
                 themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
-        if (themeBGTester.passFilterRatio() != 1) {
-            // the window background is translucent, unable to draw
+        if (themeBGTester.passFilterRatio() < 0.5f) {
+            // more than half pixels of the window background is translucent, unable to draw
             Slog.w(TAG, "Window background is translucent, fill background with black color");
             return getSystemBGColor();
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2b19da2..2be7a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -365,6 +365,11 @@
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                 decoration.closeHandleMenu();
+            } else if (id == R.id.split_screen_button) {
+                decoration.closeHandleMenu();
+                mDesktopTasksController.ifPresent(c -> {
+                    c.requestSplit(decoration.mTaskInfo);
+                });
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
             } else if (id == R.id.select_button) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 672e57a..a9eb882 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -23,14 +23,14 @@
         appIcon: Drawable
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
-    private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
-    private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
-    private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
-    private val maximizeWindowButton: ImageButton = rootView.findViewById(R.id.maximize_window)
-    private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
-    private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
+    private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
+    private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
+    private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+    private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
+    private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
+    private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -47,7 +47,9 @@
     override fun bindData(taskInfo: RunningTaskInfo) {
 
         val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+        taskInfo.taskDescription?.statusBarColor?.let {
+            captionDrawable.setColor(it)
+        }
 
         closeWindowButton.imageTintList = ColorStateList.valueOf(
                 getCaptionCloseButtonColor(taskInfo))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 47a12a0..9374ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -17,8 +17,8 @@
         onCaptionButtonClickListener: View.OnClickListener
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -27,9 +27,10 @@
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-        val captionColor = taskInfo.taskDescription.statusBarColor
-        val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(captionColor)
+        taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
+            val captionDrawable = captionView.background as GradientDrawable
+            captionDrawable.setColor(captionColor)
+        }
 
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index d293cf7..49e8d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -25,11 +25,14 @@
    * with the caption background color.
    */
   protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
-    return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
-        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-      Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
-    } else {
-      taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
-    }
+    return taskInfo.taskDescription
+        ?.let { taskDescription ->
+          if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+              taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+          } else {
+            taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+          }
+        } ?: false
   }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 9cc9fb9..55039f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,11 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.os.SystemClock
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 26aca18..b007e6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.bubble
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.view.WindowInsets
 import android.view.WindowManager
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index bf686d6..e38c4c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index c003da6..b4cedd9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.app.Activity
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -28,6 +27,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index cb5a60d..42be818 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.graphics.Rect
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.testapp.ActivityOptions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c315e74..a236126 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.app.Activity
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -27,6 +26,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 3702be9..6b97169 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 8b90630..51588569 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 50f6a38..fc6c2b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index cc3b783..8b1689a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index f8d1e1f..99613f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ff5d935..756a7fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 7c71077..121b46a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 8371706..99deb92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 0bfdbb4..212a4e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 88bbc0e..284c32e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
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 e85dc24..9e6448f 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
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index f7a9ed0..8e28712 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 66f9b85..fb0193b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 4c44028..f3145c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -27,6 +26,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 38e9f39..54f9498 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
         "frameworks-base-testutils",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
         "testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 3d8bd38..e7d0f60 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
 
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -85,12 +86,11 @@
 
     private static final String ANIMATION_ENABLED = "1";
     private final TestShellExecutor mShellExecutor = new TestShellExecutor();
-    private ShellInit mShellInit;
-
     @Rule
     public TestableContext mContext =
             new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
 
+    private ShellInit mShellInit;
     @Mock
     private IActivityTaskManager mActivityTaskManager;
 
@@ -116,6 +116,8 @@
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
+    private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -126,11 +128,23 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
-        mController = new BackAnimationController(mShellInit, mShellController,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()),
-                mActivityTaskManager, mContext,
-                mContentResolver, mAnimationBackground);
-        mController.setEnableUAnimation(true);
+        mShellBackAnimationRegistry =
+                new ShellBackAnimationRegistry(
+                        new CrossActivityAnimation(mContext, mAnimationBackground),
+                        new CrossTaskBackAnimation(mContext, mAnimationBackground),
+                        new CustomizeActivityAnimation(mContext, mAnimationBackground),
+                        null);
+        mController =
+                new BackAnimationController(
+                        mShellInit,
+                        mShellController,
+                        mShellExecutor,
+                        new Handler(mTestableLooper.getLooper()),
+                        mActivityTaskManager,
+                        mContext,
+                        mContentResolver,
+                        mAnimationBackground,
+                        mShellBackAnimationRegistry);
         mShellInit.init();
         mShellExecutor.flushAll();
     }
@@ -138,12 +152,13 @@
     private void createNavigationInfo(int backType,
             boolean enableAnimation,
             boolean isAnimationCallback) {
-        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
-                .setType(backType)
-                .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
-                .setOnBackInvokedCallback(mAppCallback)
-                .setPrepareRemoteAnimation(enableAnimation)
-                .setAnimationCallback(isAnimationCallback);
+        BackNavigationInfo.Builder builder =
+                new BackNavigationInfo.Builder()
+                        .setType(backType)
+                        .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+                        .setOnBackInvokedCallback(mAppCallback)
+                        .setPrepareRemoteAnimation(enableAnimation)
+                        .setAnimationCallback(isAnimationCallback);
 
         createNavigationInfo(builder);
     }
@@ -188,18 +203,21 @@
 
     @Test
     public void verifyNavigationFinishes() throws RemoteException {
-        final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                BackNavigationInfo.TYPE_CROSS_TASK,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                BackNavigationInfo.TYPE_DIALOG_CLOSE,
-                BackNavigationInfo.TYPE_CALLBACK };
+        final int[] testTypes =
+                new int[] {
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                    BackNavigationInfo.TYPE_CROSS_TASK,
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                    BackNavigationInfo.TYPE_DIALOG_CLOSE,
+                    BackNavigationInfo.TYPE_CALLBACK
+                };
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             registerAnimation(type);
         }
 
-        for (int type: testTypes) {
-            final ResultListener result  = new ResultListener();
+        for (int type : testTypes) {
+            final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
                     .setOnBackInvokedCallback(mAppCallback)
@@ -275,10 +293,17 @@
         // Toggle the setting off
         Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
         ShellInit shellInit = new ShellInit(mShellExecutor);
-        mController = new BackAnimationController(shellInit, mShellController,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()),
-                mActivityTaskManager, mContext,
-                mContentResolver, mAnimationBackground);
+        mController =
+                new BackAnimationController(
+                        shellInit,
+                        mShellController,
+                        mShellExecutor,
+                        new Handler(mTestableLooper.getLooper()),
+                        mActivityTaskManager,
+                        mContext,
+                        mContentResolver,
+                        mAnimationBackground,
+                        mShellBackAnimationRegistry);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
@@ -398,17 +423,19 @@
 
     @Test
     public void animationNotDefined() throws RemoteException {
-        final int[] testTypes = new int[] {
-                BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                BackNavigationInfo.TYPE_CROSS_TASK,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                BackNavigationInfo.TYPE_DIALOG_CLOSE};
+        final int[] testTypes =
+                new int[] {
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                    BackNavigationInfo.TYPE_CROSS_TASK,
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                    BackNavigationInfo.TYPE_DIALOG_CLOSE
+                };
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             unregisterAnimation(type);
         }
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
@@ -468,16 +495,14 @@
     public void testBackToActivity() throws RemoteException {
         final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
                 mAnimationBackground);
-        verifySystemBackBehavior(
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
     }
 
     @Test
     public void testBackToTask() throws RemoteException {
         final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
                 mAnimationBackground);
-        verifySystemBackBehavior(
-                BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
     }
 
     private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
@@ -554,10 +579,12 @@
     private static class ResultListener implements RemoteCallback.OnResultListener {
         boolean mBackNavigationDone = false;
         boolean mTriggerBack = false;
+
         @Override
         public void onResult(@Nullable Bundle result) {
             mBackNavigationDone = true;
             mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
         }
-    };
+    }
+    ;
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index e7d4598..cebbbd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -102,15 +102,17 @@
         // start animation with remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(
+                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
         verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
         verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
         } catch (RemoteException r) {
             fail("onBackInvoked throw remote exception");
         }
@@ -133,15 +135,17 @@
         // start animation with remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(
+                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
         verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
         verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
         } catch (RemoteException r) {
             fail("onBackCancelled throw remote exception");
         }
@@ -155,11 +159,12 @@
         // start animation without any remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
         } catch (RemoteException r) {
             fail("onBackInvoked throw remote exception");
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index 0e05e01..e359957 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -29,11 +29,11 @@
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
 
 class BubbleDataRepositoryTest : ShellTestCase() {
 
@@ -124,7 +124,7 @@
 
     private val testHandler = Handler(Looper.getMainLooper())
     private val mainExecutor = HandlerExecutor(testHandler)
-    private val launcherApps = mock(LauncherApps::class.java)
+    private val launcherApps = mock<LauncherApps>()
 
     private val persistedBubbles = SparseArray<List<BubbleEntity>>()
 
@@ -158,8 +158,7 @@
         assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
 
         // No invalid users, so no persist to disk happened
-        verify(dataRepository, never()).persistToDisk(
-            any(SparseArray<List<BubbleEntity>>()::class.java))
+        verify(dataRepository, never()).persistToDisk(any())
     }
 
     @Test
@@ -199,6 +198,4 @@
         // Verify that persist to disk happened with the new valid entities list.
         verify(dataRepository).persistToDisk(validEntitiesByUser)
     }
-
-    fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea2..fe2da5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 2cc28ac..0ae2908 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,9 +55,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
index 3a08d32..e26dc7c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -25,18 +25,24 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+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 android.os.Handler;
+import android.os.Looper;
 import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnWindowFocusChangeListener;
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.SystemWindows;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -50,28 +56,38 @@
     @Mock
     private SystemWindows mMockSystemWindows;
     @Mock
-    private SurfaceControl mMockPipLeash;
-    @Mock
-    private Handler mMockHandler;
-    @Mock
-    private TvPipActionsProvider mMockActionsProvider;
-    @Mock
     private TvPipMenuView mMockTvPipMenuView;
     @Mock
     private TvPipBackgroundView mMockTvPipBackgroundView;
 
+    private Handler mMainHandler;
     private TvPipMenuController mTvPipMenuController;
+    private OnWindowFocusChangeListener mFocusChangeListener;
 
     @Before
     public void setUp() {
         assumeTrue(isTelevision());
 
         MockitoAnnotations.initMocks(this);
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        final ViewTreeObserver mockMenuTreeObserver = mock(ViewTreeObserver.class);
+        doReturn(mockMenuTreeObserver).when(mMockTvPipMenuView).getViewTreeObserver();
 
         mTvPipMenuController = new TestTvPipMenuController();
         mTvPipMenuController.setDelegate(mMockDelegate);
-        mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
-        mTvPipMenuController.attach(mMockPipLeash);
+        mTvPipMenuController.setTvPipActionsProvider(mock(TvPipActionsProvider.class));
+        mTvPipMenuController.attach(mock(SurfaceControl.class));
+        mFocusChangeListener = captureFocusChangeListener(mockMenuTreeObserver);
+    }
+
+    private OnWindowFocusChangeListener captureFocusChangeListener(
+            ViewTreeObserver mockTreeObserver) {
+        final ArgumentCaptor<OnWindowFocusChangeListener> focusChangeListenerCaptor =
+                ArgumentCaptor.forClass(OnWindowFocusChangeListener.class);
+        verify(mockTreeObserver).addOnWindowFocusChangeListener(
+                focusChangeListenerCaptor.capture());
+        return focusChangeListenerCaptor.getValue();
     }
 
     @Test
@@ -81,24 +97,25 @@
 
     @Test
     public void testSwitch_FromNoMenuMode_ToMoveMode() {
-        showAndAssertMoveMenu();
+        showAndAssertMoveMenu(true);
     }
 
     @Test
     public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
-        showAndAssertAllActionsMenu();
+        showAndAssertAllActionsMenu(true);
     }
 
     @Test
     public void testSwitch_FromMoveMode_ToAllActionsMode() {
-        showAndAssertMoveMenu();
-        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu(true);
+        showAndAssertAllActionsMenu(false);
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
     public void testSwitch_FromAllActionsMode_ToMoveMode() {
-        showAndAssertAllActionsMenu();
-        showAndAssertMoveMenu();
+        showAndAssertAllActionsMenu(true);
+        showAndAssertMoveMenu(false);
     }
 
     @Test
@@ -110,187 +127,282 @@
 
     @Test
     public void testCloseMenu_MoveMode() {
-        showAndAssertMoveMenu();
+        showAndAssertMoveMenu(true);
 
-        closeMenuAndAssertMenuClosed();
+        closeMenuAndAssertMenuClosed(true);
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
     public void testCloseMenu_AllActionsMode() {
-        showAndAssertAllActionsMenu();
+        showAndAssertAllActionsMenu(true);
 
-        closeMenuAndAssertMenuClosed();
+        closeMenuAndAssertMenuClosed(true);
+    }
+
+    @Test
+    public void testCloseMenu_MoveModeFollowedByMoveMode() {
+        showAndAssertMoveMenu(true);
+        showAndAssertMoveMenu(false);
+
+        closeMenuAndAssertMenuClosed(true);
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
     public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
-        showAndAssertMoveMenu();
-        showAndAssertAllActionsMenu();
+        showAndAssertMoveMenu(true);
+        showAndAssertAllActionsMenu(false);
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
 
-        closeMenuAndAssertMenuClosed();
+        closeMenuAndAssertMenuClosed(true);
     }
 
     @Test
     public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
-        showAndAssertAllActionsMenu();
-        showAndAssertMoveMenu();
+        showAndAssertAllActionsMenu(true);
+        showAndAssertMoveMenu(false);
 
-        closeMenuAndAssertMenuClosed();
+        closeMenuAndAssertMenuClosed(true);
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
-    public void testExitMoveMode_NoMenuMode() {
-        mTvPipMenuController.onExitMoveMode();
-        assertMenuIsOpen(false);
-        verify(mMockDelegate, never()).onMenuClosed();
+    public void testCloseMenu_AllActionsModeFollowedByAllActionsMode() {
+        showAndAssertAllActionsMenu(true);
+        showAndAssertAllActionsMenu(false);
+
+        closeMenuAndAssertMenuClosed(true);
+        verify(mMockDelegate, never()).onInMoveModeChanged();
     }
 
     @Test
-    public void testExitMoveMode_MoveMode() {
-        showAndAssertMoveMenu();
+    public void testExitMenuMode_NoMenuMode() {
+        mTvPipMenuController.onExitCurrentMenuMode();
+        assertMenuIsOpen(false);
+        verify(mMockDelegate, never()).onMenuClosed();
+        verify(mMockDelegate, never()).onInMoveModeChanged();
+    }
 
-        mTvPipMenuController.onExitMoveMode();
+    @Test
+    public void testExitMenuMode_MoveMode() {
+        showAndAssertMoveMenu(true);
+
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
         assertMenuClosed();
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
-    public void testExitMoveMode_AllActionsMode() {
-        showAndAssertAllActionsMenu();
+    public void testExitMenuMode_AllActionsMode() {
+        showAndAssertAllActionsMenu(true);
 
-        mTvPipMenuController.onExitMoveMode();
-        assertMenuIsInAllActionsMode();
-
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
     }
 
     @Test
-    public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
-        showAndAssertAllActionsMenu();
-        showAndAssertMoveMenu();
+    public void testExitMenuMode_AllActionsModeFollowedByMoveMode() {
+        showAndAssertAllActionsMenu(true);
+        showAndAssertMoveMenu(false);
 
-        mTvPipMenuController.onExitMoveMode();
-        assertMenuIsInAllActionsMode();
-        verify(mMockDelegate, times(2)).onInMoveModeChanged();
-        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
-        verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
-    }
-
-    @Test
-    public void testOnBackPress_NoMenuMode() {
-        mTvPipMenuController.onBackPress();
-        assertMenuIsOpen(false);
-        verify(mMockDelegate, never()).onMenuClosed();
-    }
-
-    @Test
-    public void testOnBackPress_MoveMode() {
-        showAndAssertMoveMenu();
-
-        pressBackAndAssertMenuClosed();
-        verify(mMockDelegate, times(2)).onInMoveModeChanged();
-    }
-
-    @Test
-    public void testOnBackPress_AllActionsMode() {
-        showAndAssertAllActionsMenu();
-
-        pressBackAndAssertMenuClosed();
-    }
-
-    @Test
-    public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
-        showAndAssertMoveMenu();
-        showAndAssertAllActionsMenu();
+        mTvPipMenuController.onExitCurrentMenuMode();
+        assertSwitchedToAllActionsMode(2);
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
 
-        pressBackAndAssertMenuClosed();
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
     }
 
     @Test
-    public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
-        showAndAssertAllActionsMenu();
-        showAndAssertMoveMenu();
+    public void testExitMenuMode_AllActionsModeFollowedByAllActionsMode() {
+        showAndAssertAllActionsMenu(true);
+        showAndAssertAllActionsMenu(false);
 
-        mTvPipMenuController.onBackPress();
-        assertMenuIsInAllActionsMode();
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
+        verify(mMockDelegate, never()).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testExitMenuMode_MoveModeFollowedByAllActionsMode() {
+        showAndAssertMoveMenu(true);
+
+        showAndAssertAllActionsMenu(false);
         verify(mMockDelegate, times(2)).onInMoveModeChanged();
-        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
-        verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
 
-        pressBackAndAssertMenuClosed();
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
+    }
+
+    @Test
+    public void testExitMenuMode_MoveModeFollowedByMoveMode() {
+        showAndAssertMoveMenu(true);
+        showAndAssertMoveMenu(false);
+
+        mTvPipMenuController.onExitCurrentMenuMode();
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
     public void testOnPipMovement_NoMenuMode() {
-        assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+        moveAndAssertMoveSuccessful(false);
     }
 
     @Test
     public void testOnPipMovement_MoveMode() {
-        showAndAssertMoveMenu();
-        assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
-        verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+        showAndAssertMoveMenu(true);
+        moveAndAssertMoveSuccessful(true);
     }
 
     @Test
     public void testOnPipMovement_AllActionsMode() {
-        showAndAssertAllActionsMenu();
-        assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+        showAndAssertAllActionsMenu(true);
+        moveAndAssertMoveSuccessful(false);
     }
 
     @Test
-    public void testOnPipWindowFocusChanged_NoMenuMode() {
-        mTvPipMenuController.onPipWindowFocusChanged(false);
-        assertMenuIsOpen(false);
-    }
+    public void testUnexpectedFocusChanges() {
+        mFocusChangeListener.onWindowFocusChanged(true);
+        assertSwitchedToAllActionsMode(1);
 
-    @Test
-    public void testOnPipWindowFocusChanged_MoveMode() {
-        showAndAssertMoveMenu();
-        mTvPipMenuController.onPipWindowFocusChanged(false);
+        mFocusChangeListener.onWindowFocusChanged(false);
         assertMenuClosed();
+
+        showAndAssertMoveMenu(true);
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed(2);
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
     }
 
     @Test
-    public void testOnPipWindowFocusChanged_AllActionsMode() {
-        showAndAssertAllActionsMenu();
-        mTvPipMenuController.onPipWindowFocusChanged(false);
-        assertMenuClosed();
-    }
-
-    private void showAndAssertMoveMenu() {
-        mTvPipMenuController.showMovementMenu();
-        assertMenuIsInMoveMode();
-        verify(mMockDelegate).onInMoveModeChanged();
-        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
-        verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU));
-    }
-
-    private void showAndAssertAllActionsMenu() {
+    public void testAsyncScenario_AllActionsModeRequestFollowedByAsyncMoveModeRequest() {
         mTvPipMenuController.showMenu();
-        assertMenuIsInAllActionsMode();
-        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
-        verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+        // Artificially delaying the focus change update and adding a move request to simulate an
+        // async problematic situation.
+        mTvPipMenuController.showMovementMenu();
+        // The first focus change update arrives
+        mFocusChangeListener.onWindowFocusChanged(true);
+
+        // We expect that the TvPipMenuController will directly switch to the "pending" menu mode
+        // - MODE_MOVE_MENU, because no change of focus is needed.
+        assertSwitchedToMoveMode();
     }
 
-    private void closeMenuAndAssertMenuClosed() {
+    @Test
+    public void testAsyncScenario_MoveModeRequestFollowedByAsyncAllActionsModeRequest() {
+        mTvPipMenuController.showMovementMenu();
+        mTvPipMenuController.showMenu();
+
+        mFocusChangeListener.onWindowFocusChanged(true);
+        assertSwitchedToAllActionsMode(1);
+        verify(mMockDelegate, never()).onInMoveModeChanged();
+    }
+
+    @Test
+    public void testAsyncScenario_DropObsoleteIntermediateModeSwitchRequests() {
+        mTvPipMenuController.showMovementMenu();
         mTvPipMenuController.closeMenu();
+
+        // Focus change from showMovementMenu() call.
+        mFocusChangeListener.onWindowFocusChanged(true);
+        assertSwitchedToMoveMode();
+        verify(mMockDelegate).onInMoveModeChanged();
+
+        // Focus change from closeMenu() call.
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed();
+        verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+        // Unexpected focus gain should open MODE_ALL_ACTIONS_MENU.
+        mFocusChangeListener.onWindowFocusChanged(true);
+        assertSwitchedToAllActionsMode(1);
+
+        mTvPipMenuController.closeMenu();
+        mTvPipMenuController.showMovementMenu();
+
+        assertSwitchedToMoveMode(2);
+
+        mFocusChangeListener.onWindowFocusChanged(false);
+        assertMenuClosed(2);
+
+        // Closing the menu resets the default menu mode, so the next focus gain opens the menu in
+        // the default mode - MODE_ALL_ACTIONS_MENU.
+        mFocusChangeListener.onWindowFocusChanged(true);
+        assertSwitchedToAllActionsMode(2);
+        verify(mMockDelegate, times(4)).onInMoveModeChanged();
+
+    }
+
+    private void showAndAssertMoveMenu(boolean focusChange) {
+        mTvPipMenuController.showMovementMenu();
+        if (focusChange) {
+            mFocusChangeListener.onWindowFocusChanged(true);
+        }
+        assertSwitchedToMoveMode();
+    }
+
+    private void assertSwitchedToMoveMode() {
+        assertSwitchedToMoveMode(1);
+    }
+
+    private void assertSwitchedToMoveMode(int times) {
+        assertMenuIsInMoveMode();
+        verify(mMockDelegate, times(2 * times - 1)).onInMoveModeChanged();
+        verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+        verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+    }
+
+    private void showAndAssertAllActionsMenu(boolean focusChange) {
+        showAndAssertAllActionsMenu(focusChange, 1);
+    }
+
+    private void showAndAssertAllActionsMenu(boolean focusChange, int times) {
+        mTvPipMenuController.showMenu();
+        if (focusChange) {
+            mFocusChangeListener.onWindowFocusChanged(true);
+        }
+
+        assertSwitchedToAllActionsMode(times);
+    }
+
+    private void assertSwitchedToAllActionsMode(int times) {
+        assertMenuIsInAllActionsMode();
+        verify(mMockTvPipMenuView, times(times))
+                .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+        verify(mMockTvPipBackgroundView, times(times))
+                .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+    }
+
+    private void closeMenuAndAssertMenuClosed(boolean focusChange) {
+        mTvPipMenuController.closeMenu();
+        if (focusChange) {
+            mFocusChangeListener.onWindowFocusChanged(false);
+        }
         assertMenuClosed();
     }
 
-    private void pressBackAndAssertMenuClosed() {
-        mTvPipMenuController.onBackPress();
-        assertMenuClosed();
+    private void moveAndAssertMoveSuccessful(boolean expectedSuccess) {
+        mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE);
+        verify(mMockDelegate, times(expectedSuccess ? 1 : 0)).movePip(eq(TEST_MOVE_KEYCODE));
     }
 
     private void assertMenuClosed() {
+        assertMenuClosed(1);
+    }
+
+    private void assertMenuClosed(int times) {
         assertMenuIsOpen(false);
-        verify(mMockDelegate).onMenuClosed();
-        verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
-        verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU));
+        verify(mMockDelegate, times(times)).onMenuClosed();
+        verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
+        verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
     }
 
     private void assertMenuIsOpen(boolean open) {
@@ -312,15 +424,10 @@
         assertMenuIsOpen(true);
     }
 
-    private void assertPipMoveSuccessful(boolean expected, boolean actual) {
-        assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
-                + mTvPipMenuController.getMenuModeString(), expected == actual);
-    }
-
     private class TestTvPipMenuController extends TvPipMenuController {
 
         TestTvPipMenuController() {
-            super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+            super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMainHandler);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index e8a1e91..568db91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -106,6 +107,7 @@
     @Mock RecentTasksController mRecentTasks;
     @Mock LaunchAdjacentController mLaunchAdjacentController;
     @Mock WindowDecorViewModel mWindowDecorViewModel;
+    @Mock DesktopTasksController mDesktopTasksController;
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private ShellController mShellController;
@@ -122,7 +124,7 @@
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
-                mMainExecutor, mStageCoordinator));
+                mDesktopTasksController, mMainExecutor, mStageCoordinator));
     }
 
     @Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b5e6f94..7f80dff 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -44,7 +44,7 @@
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
         "-DATRACE_TAG=ATRACE_TAG_VIEW",
-        "-DLOG_TAG=\"OpenGLRenderer\"",
+        "-DLOG_TAG=\"HWUI\"",
         "-Wall",
         "-Wthread-safety",
         "-Wno-unused-parameter",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index d237cc2..fcb1bfe 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -35,15 +35,47 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat =
-            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+    GrBackendFormat backendFormat;
+    GrBackendApi backend = context->backend();
+    if (backend == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+                                                             buffer,
+                                                             desc.width,
+                                                             desc.height,
+                                                             &mDeleteProc,
+                                                             &mUpdateProc,
+                                                             &mImageCtx,
+                                                             createProtectedImage,
+                                                             backendFormat,
+                                                             false);
+    } else if (backend == GrBackendApi::kVulkan) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+                                                               buffer,
+                                                               desc.format,
+                                                               false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+                                                                 buffer,
+                                                                 desc.width,
+                                                                 desc.height,
+                                                                 &mDeleteProc,
+                                                                 &mUpdateProc,
+                                                                 &mImageCtx,
+                                                                 createProtectedImage,
+                                                                 backendFormat,
+                                                                 false);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+    }
     LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
                         __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
                                  ", AHardwareBuffer_Format==%" PRIu32 ".",
                         static_cast<int>(context->backend()), desc.format);
-    mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
-            context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
-            createProtectedImage, backendFormat, false);
     LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
                         __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
                                  ", protected==%d",
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 #include <log/log.h>
 
 #include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
 #include <sys/cdefs.h>
 #include <vulkan/vulkan.h>
 
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
 
 #include <memory>
 
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 // #define LOG_NDEBUG 0
 #include "Bitmap.h"
 
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
 #include "BitmapFactory.h"
 
 #include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
 #include "BitmapRegionDecoder.h"
 
 #include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index af1668f..0c3af61 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "FontUtils.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
 #include <assert.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
 #include <JankTracker.h>
 #include <log/log.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "9patch"
 #define LOG_NDEBUG 1
 
 #include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d2a4efe..1ba7f70 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,9 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
 #include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..2c13ceb 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
 #include <vector>
 
 #include "Gainmap.h"
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 69418b0..4dbfa88 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkStream.h"
 #include "YuvToJpegEncoder.h"
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index ee22f7c..422ffea 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <FrameInfo.h>
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Interpolator.h>
 #include <cutils/log.h>
 
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Animator.h>
 #include <Interpolator.h>
 #include <RenderProperties.h>
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 8cfdeeb7..2ec94c9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "Font.h"
 #include "SkData.h"
 #include "SkFont.h"
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 1e392b1..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedUtfChars.h>
 
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
 #include <sys/types.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
 
 #include "PdfUtils.h"
 
-#include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
 
 #include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
 
 namespace android {
 
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
 #include <minikin/GraphemeBreak.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..9ebf23c 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
 #include "utils/misc.h"
 #include "utils/Log.h"
 #include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..081713a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
 #include "GraphicsJNI.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8c377b9..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index dbd9ef3..5d3fb30 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -28,6 +28,8 @@
 #include "SkM44.h"
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include "utils/GLUtils.h"
 #include <effects/GainmapRenderer.h>
 #include "renderthread/CanvasContext.h"
@@ -47,7 +49,7 @@
 static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
     GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
     GrGLFramebufferInfo fboInfo;
-    LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+    LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
         "getGLFrameBufferInfo failed");
 
     *outFboID = fboInfo.fFBOID;
@@ -102,9 +104,10 @@
         tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
 
         GrGLFramebufferInfo fboInfo;
-        if (!SkSurfaces::GetBackendRenderTarget(tmpSurface.get(),
-                                                SkSurfaces::BackendHandleAccess::kFlushWrite)
-                     .getGLFramebufferInfo(&fboInfo)) {
+        if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+                    SkSurfaces::GetBackendRenderTarget(
+                        tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+                    &fboInfo)) {
             ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
             return;
         }
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
 namespace uirenderer {
 
 /**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
  */
 struct DrawGlInfo {
     // Input: current clip rect
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index f340945..a6e8c08f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,9 +34,6 @@
 #include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index b0ba619..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -25,9 +25,6 @@
 #include "VulkanManager.h"
 #include "utils/Color.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index 128be3c..28856c8 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -80,6 +80,10 @@
         "libgmock",
     ],
 
+    test_suites: [
+        "general-tests",
+    ],
+
     proto: {
         type: "full",
     },
diff --git a/libs/protoutil/AndroidTest.xml b/libs/protoutil/AndroidTest.xml
deleted file mode 100644
index 46d418e..0000000
--- a/libs/protoutil/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for libprotoutil_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="libprotoutil_test->/data/nativetest/libprotoutil_test" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/nativetest" />
-        <option name="module-name" value="libprotoutil_test" />
-    </test>
-</configuration>
diff --git a/libs/protoutil/TEST_MAPPING b/libs/protoutil/TEST_MAPPING
new file mode 100644
index 0000000..b10dd9b
--- /dev/null
+++ b/libs/protoutil/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "libprotoutil_test"
+    }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "libprotoutil_test"
+    }
+  ]
+}
diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp
index 96b54c6..afb54a6 100644
--- a/libs/protoutil/src/EncodedBuffer.cpp
+++ b/libs/protoutil/src/EncodedBuffer.cpp
@@ -17,6 +17,7 @@
 
 #include <stdlib.h>
 #include <sys/mman.h>
+#include <unistd.h>
 
 #include <android/util/EncodedBuffer.h>
 #include <android/util/protobuf.h>
@@ -25,7 +26,8 @@
 namespace android {
 namespace util {
 
-const size_t BUFFER_SIZE = 8 * 1024; // 8 KB
+constexpr size_t BUFFER_SIZE = 8 * 1024; // 8 KB
+const size_t kPageSize = getpagesize();
 
 EncodedBuffer::Pointer::Pointer() : Pointer(BUFFER_SIZE)
 {
@@ -92,7 +94,7 @@
 {
     // Align chunkSize to memory page size
     chunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize;
-    mChunkSize = (chunkSize / PAGE_SIZE + ((chunkSize % PAGE_SIZE == 0) ? 0 : 1)) * PAGE_SIZE;
+    mChunkSize = (chunkSize + (kPageSize - 1)) & ~(kPageSize - 1);
     mWp = Pointer(mChunkSize);
     mEp = Pointer(mChunkSize);
 }
diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp
index f895154..a0955854 100644
--- a/libs/protoutil/tests/EncodedBuffer_test.cpp
+++ b/libs/protoutil/tests/EncodedBuffer_test.cpp
@@ -15,12 +15,16 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <unistd.h>
+
 using namespace android::util;
 using android::sp;
 
-constexpr size_t TEST_CHUNK_SIZE = 16UL;
-constexpr size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2;
-constexpr size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE;
+constexpr size_t __TEST_CHUNK_SIZE = 16UL;
+const size_t kPageSize = getpagesize();
+const size_t TEST_CHUNK_SIZE = (__TEST_CHUNK_SIZE + (kPageSize - 1)) & ~(kPageSize - 1);
+const size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2;
+const size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE;
 
 static void expectPointer(EncodedBuffer::Pointer* p, size_t pos) {
     EXPECT_EQ(p->pos(), pos);
@@ -34,13 +38,13 @@
     expectPointer(buffer->wp(), 0);
     EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_SIZE);
     for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
-        buffer->writeRawByte(50 + i);
+        buffer->writeRawByte(static_cast<uint8_t>(50 + i));
     }
     EXPECT_EQ(buffer->size(), TEST_CHUNK_HALF_SIZE);
     expectPointer(buffer->wp(), TEST_CHUNK_HALF_SIZE);
     EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_HALF_SIZE);
     for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
-        buffer->writeRawByte(80 + i);
+        buffer->writeRawByte(static_cast<uint8_t>(80 + i));
     }
     EXPECT_EQ(buffer->size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
     expectPointer(buffer->wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
@@ -49,10 +53,10 @@
     // verifies the buffer's data
     expectPointer(buffer->ep(), 0);
     for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
-        EXPECT_EQ(buffer->readRawByte(), 50 + i);
+        EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(50 + i));
     }
     for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
-        EXPECT_EQ(buffer->readRawByte(), 80 + i);
+        EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(80 + i));
     }
 
     // clears the buffer
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 3f3ad75..65af392 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -16,11 +16,15 @@
 
 package android.location;
 
+import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 
 import com.android.internal.util.Preconditions;
@@ -46,15 +50,16 @@
     private final boolean mCorrelationVectorOutputsEnabled;
     private final boolean mFullTracking;
     private final int mIntervalMillis;
-
+    private WorkSource mWorkSource;
     /**
      * Creates a {@link GnssMeasurementRequest} with a full list of parameters.
      */
     private GnssMeasurementRequest(boolean fullTracking, boolean correlationVectorOutputsEnabled,
-            int intervalMillis) {
+            int intervalMillis, WorkSource workSource) {
         mFullTracking = fullTracking;
         mCorrelationVectorOutputsEnabled = correlationVectorOutputsEnabled;
         mIntervalMillis = intervalMillis;
+        mWorkSource = Objects.requireNonNull(workSource);
     }
 
     /**
@@ -107,14 +112,31 @@
         return mIntervalMillis;
     }
 
+    /**
+     * Returns the work source used for power blame for this request. If empty (i.e.,
+     * {@link WorkSource#isEmpty()} is {@code true}, the system is free to assign power blame as it
+     * deems most appropriate.
+     *
+     * @return the work source used for power blame for this request
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull WorkSource getWorkSource() {
+        return mWorkSource;
+    }
+
     @NonNull
     public static final Creator<GnssMeasurementRequest> CREATOR =
             new Creator<GnssMeasurementRequest>() {
                 @Override
                 @NonNull
                 public GnssMeasurementRequest createFromParcel(@NonNull Parcel parcel) {
-                    return new GnssMeasurementRequest(parcel.readBoolean(), parcel.readBoolean(),
-                            parcel.readInt());
+                    return new GnssMeasurementRequest(
+                            /* fullTracking= */ parcel.readBoolean(),
+                            /* correlationVectorOutputsEnabled= */ parcel.readBoolean(),
+                            /* intervalMillis= */ parcel.readInt(),
+                            /* workSource= */ parcel.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -128,6 +150,7 @@
         parcel.writeBoolean(mFullTracking);
         parcel.writeBoolean(mCorrelationVectorOutputsEnabled);
         parcel.writeInt(mIntervalMillis);
+        parcel.writeTypedObject(mWorkSource, 0);
     }
 
     @NonNull
@@ -147,6 +170,9 @@
         if (mCorrelationVectorOutputsEnabled) {
             s.append(", CorrelationVectorOutputs");
         }
+        if (mWorkSource != null && !mWorkSource.isEmpty()) {
+            s.append(", ").append(mWorkSource);
+        }
         s.append(']');
         return s.toString();
     }
@@ -165,12 +191,16 @@
         if (mIntervalMillis != other.mIntervalMillis) {
             return false;
         }
+        if (!Objects.equals(mWorkSource, other.mWorkSource)) {
+            return false;
+        }
         return true;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled, mIntervalMillis);
+        return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled, mIntervalMillis,
+                mWorkSource);
     }
 
     @Override
@@ -183,6 +213,7 @@
         private boolean mCorrelationVectorOutputsEnabled;
         private boolean mFullTracking;
         private int mIntervalMillis;
+        private WorkSource mWorkSource;
 
         /**
          * Constructs a {@link Builder} instance.
@@ -197,6 +228,7 @@
             mCorrelationVectorOutputsEnabled = request.isCorrelationVectorOutputsEnabled();
             mFullTracking = request.isFullTracking();
             mIntervalMillis = request.getIntervalMillis();
+            mWorkSource = request.getWorkSource();
         }
 
         /**
@@ -255,11 +287,29 @@
             return this;
         }
 
+        /**
+         * Sets the work source to use for power blame for this request. Passing in null or leaving
+         * it unset will be an empty WorkSource, which implies the system is free to assign power
+         * blame as it determines best for this request (which usually means blaming the owner of
+         * the GnssMeasurement listener).
+         *
+         * <p>Permissions enforcement occurs when resulting request is actually used, not when this
+         * method is invoked.
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
+        public @NonNull Builder setWorkSource(@Nullable WorkSource workSource) {
+            mWorkSource = workSource;
+            return this;
+        }
+
         /** Builds a {@link GnssMeasurementRequest} instance as specified by this builder. */
         @NonNull
         public GnssMeasurementRequest build() {
             return new GnssMeasurementRequest(mFullTracking, mCorrelationVectorOutputsEnabled,
-                    mIntervalMillis);
+                    mIntervalMillis, new WorkSource(mWorkSource));
         }
     }
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
         try {
             mImpl.start(new MediaProjectionCallback());
         } catch (RemoteException e) {
+            Log.e(TAG, "Content Recording: Failed to start media projection", e);
             throw new RuntimeException("Failed to start media projection", e);
         }
         mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
      * @see #unregisterCallback
      */
     public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
-        final Callback c = Objects.requireNonNull(callback);
-        if (handler == null) {
-            handler = new Handler();
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            if (handler == null) {
+                handler = new Handler();
+            }
+            mCallbacks.put(c, new CallbackRecord(c, handler));
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Content Recording: cannot register null Callback", e);
+            throw e;
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
         }
-        mCallbacks.put(c, new CallbackRecord(c, handler));
     }
 
     /**
@@ -120,8 +128,13 @@
      * @see #registerCallback
      */
     public void unregisterCallback(@NonNull Callback callback) {
-        final Callback c = Objects.requireNonNull(callback);
-        mCallbacks.remove(c);
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            mCallbacks.remove(c);
+        } catch (NullPointerException e) {
+            Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+            throw e;
+        }
     }
 
     /**
@@ -203,9 +216,11 @@
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
         if (shouldMediaProjectionRequireCallback()) {
             if (mCallbacks.isEmpty()) {
-                throw new IllegalStateException(
+                final IllegalStateException e = new IllegalStateException(
                         "Must register a callback before starting capture, to manage resources in"
                                 + " response to MediaProjection states.");
+                Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+                throw e;
             }
         }
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
      */
     public void stop() {
         try {
+            Log.d(TAG, "Content Recording: stopping projection");
             mImpl.stop();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5a68c53..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -256,6 +256,7 @@
      */
     public void stopActiveProjection() {
         try {
+            Log.d(TAG, "Content Recording: stopping active projection");
             mService.stopActiveProjection();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -269,6 +270,7 @@
      */
     public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
         if (callback == null) {
+            Log.w(TAG, "Content Recording: cannot add null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -286,6 +288,7 @@
      */
     public void removeCallback(@NonNull Callback callback) {
         if (callback == null) {
+            Log.w(TAG, "ContentRecording: cannot remove null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = mCallbacks.remove(callback);
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d81843d..f87e47e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -452,6 +452,12 @@
         acquireTRMSLock("shareFrontendFromTuner()");
         mFrontendLock.lock();
         try {
+            if (mFeOwnerTuner != null) {
+                // unregister self from the Frontend callback
+                mFeOwnerTuner.unregisterFrontendCallbackListener(this);
+                mFeOwnerTuner = null;
+                nativeUnshareFrontend();
+            }
             mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
             mFeOwnerTuner = tuner;
             mFeOwnerTuner.registerFrontendCallbackListener(this);
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index c39a6db..d058600 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -32,7 +32,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.NullPointerException;
 import java.util.concurrent.Executor;
 
 /**
@@ -270,16 +269,20 @@
         synchronized (mCallbackLock) {
             if (mCallback != null && mExecutor != null) {
                 mExecutor.execute(() -> {
+                    FilterCallback callback;
                     synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            try {
-                                mCallback.onFilterStatusChanged(this, status);
-                            }
-                            catch (NullPointerException e) {
-                                Log.d(TAG, "catch exception:" + e);
-                            }
+                        callback = mCallback;
+                    }
+                    if (callback != null) {
+                        try {
+                            callback.onFilterStatusChanged(this, status);
+                        } catch (NullPointerException e) {
+                            Log.d(TAG, "catch exception:" + e);
                         }
                     }
+                    if (callback != null) {
+                        callback.onFilterStatusChanged(this, status);
+                    }
                 });
             }
         }
@@ -289,19 +292,20 @@
         synchronized (mCallbackLock) {
             if (mCallback != null && mExecutor != null) {
                 mExecutor.execute(() -> {
+                    FilterCallback callback;
                     synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            try {
-                                mCallback.onFilterEvent(this, events);
-                            }
-                            catch (NullPointerException e) {
-                                Log.d(TAG, "catch exception:" + e);
-                            }
-                        } else {
-                            for (FilterEvent event : events) {
-                                if (event instanceof MediaEvent) {
-                                    ((MediaEvent)event).release();
-                                }
+                        callback = mCallback;
+                    }
+                    if (callback != null) {
+                        try {
+                            callback.onFilterEvent(this, events);
+                        } catch (NullPointerException e) {
+                            Log.d(TAG, "catch exception:" + e);
+                        }
+                    } else {
+                        for (FilterEvent event : events) {
+                            if (event instanceof MediaEvent) {
+                                ((MediaEvent) event).release();
                             }
                         }
                     }
@@ -309,7 +313,7 @@
             } else {
                 for (FilterEvent event : events) {
                     if (event instanceof MediaEvent) {
-                        ((MediaEvent)event).release();
+                        ((MediaEvent) event).release();
                     }
                 }
             }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5ea98c0c..b03a039 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -3450,7 +3450,8 @@
         JNIEnv *env, jobject thiz,
         jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) {
     if (name == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        jniThrowException(env, "java/lang/NullPointerException",
+                          "No codec name specified");
         return;
     }
 
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 254eb44..7f3792d 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -46,7 +46,10 @@
 
 cc_library_shared {
     name: "libandroid",
-    defaults: ["libandroid_defaults"],
+    defaults: [
+        "libandroid_defaults",
+        "android.hardware.power-ndk_shared",
+    ],
 
     srcs: [
         "activity_manager.cpp",
@@ -95,7 +98,6 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
-        "android.hardware.power-V4-ndk",
         "libnativedisplay",
     ],
 
diff --git a/native/android/OWNERS b/native/android/OWNERS
index d41652f..0b86909 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -26,3 +26,6 @@
 
 # Input
 per-file input.cpp = file:/INPUT_OWNERS
+
+# PerformanceHint
+per-file performance_hint.cpp = file:/ADPF_OWNERS
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index d74f9b7..b0af09c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -334,6 +334,7 @@
     APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
     APerformanceHint_closeSession; # introduced=Tiramisu
     APerformanceHint_setThreads; # introduced=UpsideDownCake
+    APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 6198f40..c25df6e 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "perf_hint"
 
 #include <aidl/android/hardware/power/SessionHint.h>
+#include <aidl/android/hardware/power/SessionMode.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -36,6 +37,7 @@
 using namespace std::chrono_literals;
 
 using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+using AidlSessionMode = aidl::android::hardware::power::SessionMode;
 
 struct APerformanceHintSession;
 
@@ -72,6 +74,7 @@
     int sendHint(SessionHint hint);
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
+    int setPreferPowerEfficiency(bool enabled);
 
 private:
     friend struct APerformanceHintManager;
@@ -307,6 +310,18 @@
     return 0;
 }
 
+int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
+    binder::Status ret =
+            mHintSession->setMode(static_cast<int32_t>(AidlSessionMode::POWER_EFFICIENCY), enabled);
+
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    return OK;
+}
+
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
     return APerformanceHintManager::getInstance();
@@ -357,6 +372,10 @@
             ->getThreadIds(threadIds, size);
 }
 
+int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, bool enabled) {
+    return session->setPreferPowerEfficiency(enabled);
+}
+
 void APerformanceHint_setIHintManagerForTesting(void* iManager) {
     delete gHintManagerForTesting;
     gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/OWNERS b/native/android/tests/performance_hint/OWNERS
new file mode 100644
index 0000000..e3bbee92
--- /dev/null
+++ b/native/android/tests/performance_hint/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 6f7562b..22d33b1 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -56,7 +56,8 @@
                 (const ::std::vector<int64_t>& actualDurationNanos,
                  const ::std::vector<int64_t>& timeStampNanos),
                 (override));
-    MOCK_METHOD(Status, sendHint, (int32_t hints), (override));
+    MOCK_METHOD(Status, sendHint, (int32_t hint), (override));
+    MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
 };
@@ -190,3 +191,51 @@
     result = APerformanceHint_setThreads(session, invalidTids.data(), invalidTids.size());
     EXPECT_EQ(EPERM, result);
 }
+
+TEST_F(PerformanceHintTest, SetPowerEfficient) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 56789L;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+
+    EXPECT_CALL(*iSession, setMode(_, Eq(true))).Times(Exactly(1));
+    int result = APerformanceHint_setPreferPowerEfficiency(session, true);
+    EXPECT_EQ(0, result);
+
+    EXPECT_CALL(*iSession, setMode(_, Eq(false))).Times(Exactly(1));
+    result = APerformanceHint_setPreferPowerEfficiency(session, false);
+    EXPECT_EQ(0, result);
+}
+
+TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 0;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+}
\ No newline at end of file
diff --git a/packages/EasterEgg/src/com/android/egg/quares/Quare.kt b/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
index eb77362..f0cc2a1 100644
--- a/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
+++ b/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
@@ -137,14 +137,12 @@
         return 0
     }
 
-    override fun writeToParcel(p: Parcel?, flags: Int) {
-        p?.let {
-            p.writeInt(width)
-            p.writeInt(height)
-            p.writeInt(depth)
-            p.writeIntArray(data)
-            p.writeIntArray(user)
-        }
+    override fun writeToParcel(p: Parcel, flags: Int) {
+        p.writeInt(width)
+        p.writeInt(height)
+        p.writeInt(depth)
+        p.writeIntArray(data)
+        p.writeIntArray(user)
     }
 
     companion object CREATOR : Parcelable.Creator<Quare> {
diff --git a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
index 578de01..5fa6137 100644
--- a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
@@ -60,8 +60,8 @@
 
         setContentView(R.layout.activity_quares)
 
-        grid = findViewById(R.id.grid)
-        label = findViewById(R.id.label)
+        grid = requireViewById(R.id.grid)
+        label = requireViewById(R.id.label)
 
         if (savedInstanceState != null) {
             Log.v(TAG, "restoring puzzle from state")
@@ -135,7 +135,7 @@
         if (q.check()) {
             val dp = resources.displayMetrics.density
 
-            val label: Button = findViewById(R.id.label)
+            val label: Button = requireViewById(R.id.label)
             label.text = resName.replace(Regex("^.*/"), "")
             val drawable = icon?.loadDrawable(this)?.also {
                 it.setBounds(0, 0, (32 * dp).toInt(), (32 * dp).toInt())
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.LoaderManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -714,8 +715,13 @@
 
                     try {
                         mPrinterForInfoIntent = printer;
+                        Bundle options = ActivityOptions.makeBasic()
+                                .setPendingIntentBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle();
                         startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
-                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+                                options);
                     } catch (SendIntentException e) {
                         mPrinterForInfoIntent = null;
                         Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/spa/res/values/colors.xml b/packages/SettingsLib/Spa/spa/res/values/colors.xml
new file mode 100644
index 0000000..ca4a0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/res/values/colors.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+    <color name="settingslib_protection_color">@android:color/white</color>
+
+    <!-- Dynamic colors-->
+    <color name="settingslib_color_blue600">#1a73e8</color>
+    <color name="settingslib_color_blue400">#669df6</color>
+    <color name="settingslib_color_blue300">#8ab4f8</color>
+    <color name="settingslib_color_blue100">#d2e3fc</color>
+    <color name="settingslib_color_blue50">#e8f0fe</color>
+    <color name="settingslib_color_green600">#1e8e3e</color>
+    <color name="settingslib_color_green500">#34A853</color>
+    <color name="settingslib_color_green400">#5bb974</color>
+    <color name="settingslib_color_green100">#ceead6</color>
+    <color name="settingslib_color_green50">#e6f4ea</color>
+    <color name="settingslib_color_red600">#d93025</color>
+    <color name="settingslib_color_red500">#B3261E</color>
+    <color name="settingslib_color_red400">#ee675c</color>
+    <color name="settingslib_color_red100">#fad2cf</color>
+    <color name="settingslib_color_red50">#fce8e6</color>
+    <color name="settingslib_color_yellow600">#f9ab00</color>
+    <color name="settingslib_color_yellow400">#fcc934</color>
+    <color name="settingslib_color_yellow100">#feefc3</color>
+    <color name="settingslib_color_yellow50">#fef7e0</color>
+    <color name="settingslib_color_grey900">#202124</color>
+    <color name="settingslib_color_grey800">#3c4043</color>
+    <color name="settingslib_color_grey700">#5f6368</color>
+    <color name="settingslib_color_grey600">#80868b</color>
+    <color name="settingslib_color_grey500">#9AA0A6</color>
+    <color name="settingslib_color_grey400">#bdc1c6</color>
+    <color name="settingslib_color_grey300">#dadce0</color>
+    <color name="settingslib_color_grey200">#e8eaed</color>
+    <color name="settingslib_color_grey100">#f1f3f4</color>
+    <color name="settingslib_color_grey50">#f8f9fa</color>
+    <color name="settingslib_color_orange600">#e8710a</color>
+    <color name="settingslib_color_orange400">#fa903e</color>
+    <color name="settingslib_color_orange300">#fcad70</color>
+    <color name="settingslib_color_orange100">#fedfc8</color>
+    <color name="settingslib_color_pink600">#e52592</color>
+    <color name="settingslib_color_pink400">#ff63b8</color>
+    <color name="settingslib_color_pink300">#ff8bcb</color>
+    <color name="settingslib_color_pink100">#fdcfe8</color>
+    <color name="settingslib_color_purple600">#9334e6</color>
+    <color name="settingslib_color_purple400">#af5cf7</color>
+    <color name="settingslib_color_purple300">#c58af9</color>
+    <color name="settingslib_color_purple100">#e9d2fd</color>
+    <color name="settingslib_color_cyan600">#12b5c8</color>
+    <color name="settingslib_color_cyan400">#4ecde6</color>
+    <color name="settingslib_color_cyan300">#78d9ec</color>
+    <color name="settingslib_color_cyan100">#cbf0f8</color>
+</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index 7cc9bf7..6a2163c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -22,11 +22,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.ImageBox
 import com.android.settingslib.spa.widget.ui.Lottie
@@ -81,7 +81,7 @@
                 maxHeight = SettingsDimension.illustrationMaxHeight,
             )
             .clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
-            .background(color = MaterialTheme.colorScheme.surface)
+            .background(color = Color.Transparent)
 
         when (resourceType) {
             ResourceType.LOTTIE -> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index 915c6e2..5f7fe85 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -16,15 +16,24 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import com.airbnb.lottie.LottieProperty
 import com.airbnb.lottie.compose.LottieAnimation
 import com.airbnb.lottie.compose.LottieCompositionSpec
 import com.airbnb.lottie.compose.LottieConstants
 import com.airbnb.lottie.compose.animateLottieCompositionAsState
 import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.settingslib.spa.R
 
 @Composable
 fun Lottie(
@@ -38,6 +47,34 @@
     }
 }
 
+object LottieColorUtils {
+    private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+        ".grey600" to R.color.settingslib_color_grey400,
+        ".grey800" to R.color.settingslib_color_grey300,
+        ".grey900" to R.color.settingslib_color_grey50,
+        ".red400" to R.color.settingslib_color_red600,
+        ".black" to android.R.color.white,
+        ".blue400" to R.color.settingslib_color_blue600,
+        ".green400" to R.color.settingslib_color_green600,
+        ".green200" to R.color.settingslib_color_green500,
+        ".red200" to R.color.settingslib_color_red500,
+    )
+
+    @Composable
+    private fun getDefaultPropertiesList() =
+        DARK_TO_LIGHT_THEME_COLOR_MAP.map { (key, colorRes) ->
+            val color = colorResource(colorRes).toArgb()
+            rememberLottieDynamicProperty(
+                property = LottieProperty.COLOR_FILTER,
+                keyPath = arrayOf("**", key, "**")
+            ){ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) }
+        }
+
+    @Composable
+    fun getDefaultDynamicProperties() =
+        rememberLottieDynamicProperties(*getDefaultPropertiesList().toTypedArray())
+}
+
 @Composable
 private fun BaseLottie(resId: Int) {
     val composition by rememberLottieComposition(
@@ -47,8 +84,10 @@
         composition,
         iterations = LottieConstants.IterateForever,
     )
+    val isLightMode = !isSystemInDarkTheme()
     LottieAnimation(
         composition = composition,
+        dynamicProperties = LottieColorUtils.getDefaultDynamicProperties().takeIf { isLightMode },
         progress = { progress },
     )
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
index c94572b..a47ccc1 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.toList
@@ -28,7 +27,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class OverridableFlowTest {
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
index 62f4707..693cd77 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
@@ -18,12 +18,10 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class CollectionsTest {
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
index 4dcdea9..71d69b2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.count
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.first
@@ -28,7 +27,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class FlowsTest {
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
index e1d9a28..f0e57b9 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
@@ -21,13 +21,11 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class StateFlowBridgeTest {
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 5fc1972..626c913 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -22,25 +22,27 @@
 import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
 import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 private const val TAG = "AppStorageSize"
 
 @Composable
 fun ApplicationInfo.getStorageSize(): State<String> {
     val context = LocalContext.current
-    return produceState(initialValue = placeholder()) {
-        withContext(Dispatchers.IO) {
+    return remember {
+        flow {
             val sizeBytes = calculateSizeBytes(context)
-            value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
-        }
-    }
+            this.emit(if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else "")
+        }.flowOn(Dispatchers.IO)
+    }.collectAsStateWithLifecycle(initialValue = placeholder())
 }
 
 fun ApplicationInfo.calculateSizeBytes(context: Context): Long? {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 375ed60..2281cd8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -31,7 +31,6 @@
 import com.android.internal.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
@@ -50,7 +49,6 @@
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
     @get:Rule
@@ -312,7 +310,12 @@
     fun getSystemPackageNames_returnExpectedValues() = runTest {
         mockInstalledApplications(
             apps = listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+                NORMAL_APP,
+                INSTANT_APP,
+                SYSTEM_APP,
+                UPDATED_SYSTEM_APP,
+                HOME_APP,
+                IN_LAUNCHER_APP,
             ),
             userId = ADMIN_USER_ID,
         )
@@ -329,7 +332,12 @@
     fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
         mockInstalledApplications(
             apps = listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+                NORMAL_APP,
+                INSTANT_APP,
+                SYSTEM_APP,
+                UPDATED_SYSTEM_APP,
+                HOME_APP,
+                IN_LAUNCHER_APP,
             ),
             userId = ADMIN_USER_ID,
         )
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 9b22497..4d9d6da 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -25,7 +25,6 @@
 import com.android.settingslib.spa.testutils.waitUntil
 import com.android.settingslib.spaprivileged.template.app.AppListConfig
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
@@ -37,7 +36,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListViewModelTest {
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index c54f4f8..f4faa0a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -31,7 +31,6 @@
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,12 +43,11 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppOpPermissionAppListTest {
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dab3bcb..0acce03 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -225,9 +225,9 @@
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan">Internet access</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
-    <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
+    <string name="bluetooth_profile_pbap">Allow access to contacts and call history</string>
     <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
-    <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
+    <string name="bluetooth_profile_pbap_summary">Info will be used for call announcements and more</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 1251b0d..9ab84d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -26,6 +26,7 @@
 import static android.os.BatteryManager.EXTRA_PLUGGED;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
 
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +41,8 @@
  */
 public class BatteryStatus {
     private static final int LOW_BATTERY_THRESHOLD = 20;
+    private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+    private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
 
     public static final int CHARGING_UNKNOWN = -1;
@@ -90,21 +93,7 @@
         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
         this.incompatibleCharger = incompatibleCharger;
 
-        final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
-                -1);
-        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-
-        if (maxChargingMicroVolt <= 0) {
-            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
-        }
-        if (maxChargingMicroAmp > 0) {
-            // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
-            // to maintain precision equally on both factors.
-            maxChargingWattage = (maxChargingMicroAmp / 1000)
-                    * (maxChargingMicroVolt / 1000);
-        } else {
-            maxChargingWattage = -1;
-        }
+        maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
     }
 
     /** Determine whether the device is plugged. */
@@ -126,7 +115,7 @@
 
     /** Determine whether the device is plugged in dock. */
     public boolean isPluggedInDock() {
-        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+        return isPluggedInDock(plugged);
     }
 
     /**
@@ -140,15 +129,15 @@
 
     /** Whether battery is low and needs to be charged. */
     public boolean isBatteryLow() {
-        return level < LOW_BATTERY_THRESHOLD;
+        return isLowBattery(level);
     }
 
     /** Whether battery defender is enabled. */
     public boolean isBatteryDefender() {
-        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+        return isBatteryDefender(chargingStatus);
     }
 
-    /** Return current chargin speed is fast, slow or normal. */
+    /** Return current charging speed is fast, slow or normal. */
     public final int getChargingSpeed(Context context) {
         final int slowThreshold = context.getResources().getInteger(
                 R.integer.config_chargingSlowlyThreshold);
@@ -218,4 +207,126 @@
                 || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(Intent batteryChangedIntent) {
+        return isPluggedInDock(
+                batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
+    }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(int plugged) {
+        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return isLowBattery(level);
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryLevel the battery level
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(int batteryLevel) {
+        return batteryLevel <= LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is severe low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     SEVERE_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is extreme low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     EXTREME_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(Intent batteryChangedIntent) {
+        int chargingStatus =
+                batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+        return isBatteryDefender(chargingStatus);
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
+     *     intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(int chargingStatus) {
+        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+    }
+
+    /**
+     * Gets the max charging current and max charging voltage form {@link
+     * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
+     * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+     *
+     * @param context the application context
+     * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
+     * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+     *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+     */
+    public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
+        final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+        if (maxChargingMicroWatt <= 0) {
+            return CHARGING_UNKNOWN;
+        } else if (maxChargingMicroWatt
+                < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
+            return CHARGING_SLOWLY;
+        } else if (maxChargingMicroWatt
+                > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+            return CHARGING_FAST;
+        } else {
+            return CHARGING_REGULAR;
+        }
+    }
+
+    private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
+        final int maxChargingMicroAmp =
+                batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+        if (maxChargingMicroVolt <= 0) {
+            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+        }
+
+        if (maxChargingMicroAmp > 0) {
+            // Calculating µW = mA * mV
+            return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
+        } else {
+            return -1;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index a03acc3..73f6db6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -325,7 +325,7 @@
         return batteryLevel
     }
 
-    override fun onBoundsChange(bounds: Rect?) {
+    override fun onBoundsChange(bounds: Rect) {
         super.onBoundsChange(bounds)
         updateSize()
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java b/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java
new file mode 100644
index 0000000..1134d13
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wifi.dpp;
+
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.text.TextUtils;
+
+import java.util.List;
+
+
+/**
+ * Wifi dpp intent helper functions to share between the Settings App and SystemUI.
+ */
+public class WifiDppIntentHelper {
+    static final String EXTRA_WIFI_SECURITY = "security";
+
+    /** The data corresponding to {@code WifiConfiguration} SSID */
+    static final String EXTRA_WIFI_SSID = "ssid";
+
+    /** The data corresponding to {@code WifiConfiguration} preSharedKey */
+    static final String EXTRA_WIFI_PRE_SHARED_KEY = "preSharedKey";
+
+    /** The data corresponding to {@code WifiConfiguration} hiddenSSID */
+    static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid";
+    static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE
+    static final String SECURITY_WEP = "WEP";
+    static final String SECURITY_WPA_PSK = "WPA";
+    static final String SECURITY_SAE = "SAE";
+
+    /**
+     * Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to
+     * launch configurator activity later.
+     *
+     * @param intent the target to set extra
+     * @param wifiManager an instance of {@code WifiManager}
+     * @param wifiConfiguration the Wi-Fi network for launching configurator activity
+     */
+    public static void setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager,
+            WifiConfiguration wifiConfiguration) {
+        String ssid = removeFirstAndLastDoubleQuotes(wifiConfiguration.SSID);
+        String security = getSecurityString(wifiConfiguration);
+
+        // When the value of this key is read, the actual key is not returned, just a "*".
+        // Call privileged system API to obtain actual key.
+        String preSharedKey = removeFirstAndLastDoubleQuotes(getPresharedKey(wifiManager,
+                wifiConfiguration));
+
+        if (!TextUtils.isEmpty(ssid)) {
+            intent.putExtra(EXTRA_WIFI_SSID, ssid);
+        }
+        if (!TextUtils.isEmpty(security)) {
+            intent.putExtra(EXTRA_WIFI_SECURITY, security);
+        }
+        if (!TextUtils.isEmpty(preSharedKey)) {
+            intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
+        }
+        intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, wifiConfiguration.hiddenSSID);
+    }
+
+    private static String getSecurityString(WifiConfiguration config) {
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+            return SECURITY_SAE;
+        }
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            return SECURITY_NO_PASSWORD;
+        }
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA2_PSK)) {
+            return SECURITY_WPA_PSK;
+        }
+        return (config.wepKeys[0] == null) ? SECURITY_NO_PASSWORD : SECURITY_WEP;
+    }
+
+    private static String removeFirstAndLastDoubleQuotes(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return str;
+        }
+
+        int begin = 0;
+        int end = str.length() - 1;
+        if (str.charAt(begin) == '\"') {
+            begin++;
+        }
+        if (str.charAt(end) == '\"') {
+            end--;
+        }
+        return str.substring(begin, end + 1);
+    }
+
+    private static String getPresharedKey(WifiManager wifiManager,
+            WifiConfiguration wifiConfiguration) {
+        List<WifiConfiguration> privilegedWifiConfigurations =
+                wifiManager.getPrivilegedConfiguredNetworks();
+
+        for (WifiConfiguration privilegedWifiConfiguration : privilegedWifiConfigurations) {
+            if (privilegedWifiConfiguration.networkId == wifiConfiguration.networkId) {
+                // WEP uses a shared key hence the AuthAlgorithm.SHARED is used
+                // to identify it.
+                if (wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+                        && wifiConfiguration.allowedAuthAlgorithms.get(
+                        WifiConfiguration.AuthAlgorithm.SHARED)) {
+                    return privilegedWifiConfiguration
+                            .wepKeys[privilegedWifiConfiguration.wepTxKeyIndex];
+                } else {
+                    return privilegedWifiConfiguration.preSharedKey;
+                }
+            }
+        }
+        return wifiConfiguration.preSharedKey;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java
new file mode 100644
index 0000000..d73df2d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java
@@ -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.wifi.dpp;
+
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_HIDDEN_SSID;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_PRE_SHARED_KEY;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_SECURITY;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_SSID;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.SECURITY_NO_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiDppIntentHelperTest {
+    @Mock
+    private WifiManager mWifiManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mWifiManager.getPrivilegedConfiguredNetworks()).thenReturn(new ArrayList<>());
+    }
+
+    @Test
+    public void setConfiguratorIntentExtra_returnsCorrectValues() {
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = EXTRA_WIFI_SSID;
+        wifiConfiguration.preSharedKey = EXTRA_WIFI_PRE_SHARED_KEY;
+        wifiConfiguration.hiddenSSID = true;
+
+        Intent expected = new Intent();
+        WifiDppIntentHelper.setConfiguratorIntentExtra(expected, mWifiManager, wifiConfiguration);
+
+        assertThat(expected.getStringExtra(EXTRA_WIFI_SSID)).isEqualTo(EXTRA_WIFI_SSID);
+        assertThat(expected.getStringExtra(EXTRA_WIFI_SECURITY)).isEqualTo(SECURITY_NO_PASSWORD);
+        assertThat(expected.getStringExtra(EXTRA_WIFI_PRE_SHARED_KEY)).isEqualTo(
+                EXTRA_WIFI_PRE_SHARED_KEY);
+        assertThat(expected.getBooleanExtra(EXTRA_WIFI_HIDDEN_SSID, false))
+                .isEqualTo(true);
+    }
+}
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
new file mode 100644
index 0000000..6c0c1a7
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fuelgague
+
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.os.BatteryManager.BATTERY_PLUGGED_AC
+import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
+import android.os.BatteryManager.BATTERY_PLUGGED_USB
+import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
+import android.os.BatteryManager.BATTERY_STATUS_FULL
+import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
+import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
+import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.fuelgauge.BatteryStatus
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
+import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+import org.junit.runners.Suite.SuiteClasses
+
+@RunWith(Suite::class)
+@SuiteClasses(
+    BatteryStatusTest.NonParameterizedTest::class,
+    BatteryStatusTest.IsPluggedInTest::class,
+    BatteryStatusTest.IsChargedTest::class,
+    BatteryStatusTest.GetChargingSpeedTest::class,
+    BatteryStatusTest.IsPluggedInDockTest::class,
+)
+open class BatteryStatusTest {
+
+    @RunWith(AndroidJUnit4::class)
+    class NonParameterizedTest : BatteryStatusTest() {
+        @Test
+        fun isLowBattery_20Percent_returnsTrue() {
+            val level = 20
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(Intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isTrue()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(level))
+                .isTrue()
+        }
+
+        @Test
+        fun isLowBattery_21Percent_returnsFalse() {
+            val level = 21
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+        }
+
+        @Test
+        fun isSevereLowBattery_10Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 10)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isSevereLowBattery_11Percent_returnFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 11)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isExtremeLowBattery_3Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 3)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isExtremeLowBattery_4Percent_returnsFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 4)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isBatteryDefender_chargingLongLife_returnsTrue() {
+            val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
+        }
+
+        @Test
+        fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
+            val chargingStatus = CHARGING_POLICY_DEFAULT
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
+        }
+
+        private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
+            object {
+                val assertions =
+                    listOf(
+                        "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
+                            assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
+                        },
+                        "failed by isBatteryDefender($chargingStatus)".let {
+                            assertWithMessage(it).that(isBatteryDefender(chargingStatus))
+                        },
+                    )
+
+                fun isTrue() = assertions.forEach { it.isTrue() }
+
+                fun isFalse() = assertions.forEach { it.isFalse() }
+            }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedIn(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInDockTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedDockIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedInDock(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsChargedTest(
+        private val status: Int,
+        private val batteryLevel: Int,
+        private val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isCharged_() {
+            val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
+
+            assertWithMessage(
+                    "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
+                )
+                .that(BatteryStatus.isCharged(batteryChangedIntent))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isCharged($status, $batteryLevel)")
+                .that(BatteryStatus.isCharged(status, batteryLevel))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(BATTERY_STATUS_FULL, 99, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
+                    arrayOf(BATTERY_STATUS_FULL, 100, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class GetChargingSpeedTest(
+        private val name: String,
+        private val maxChargingCurrent: Optional<Int>,
+        private val maxChargingVoltage: Optional<Int>,
+        private val expectedChargingSpeed: Int,
+    ) {
+
+        val context: Context = ApplicationProvider.getApplicationContext()
+
+        @Test
+        fun getChargingSpeed_() {
+            val batteryChangedIntent =
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
+                    maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
+                }
+
+            assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
+                .isEqualTo(expectedChargingSpeed)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(
+                        "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
+                        Optional.empty<Int>(),
+                        Optional.empty<Int>(),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
+                        Optional.of(0),
+                        Optional.of(0),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1500000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1000000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
+                        Optional.of(1501000),
+                        Optional.of(5000000),
+                        CHARGING_FAST
+                    ),
+                    arrayOf(
+                        "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
+                        Optional.of(999999),
+                        Optional.of(5000000),
+                        CHARGING_SLOWLY
+                    ),
+                )
+        }
+    }
+
+    protected fun createIntent(
+        batteryLevel: Int = 50,
+        chargingStatus: Int = CHARGING_POLICY_DEFAULT,
+        plugged: Int = BATTERY_PLUGGED_NONE,
+        status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
+    ): Intent =
+        Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+            putExtra(BatteryManager.EXTRA_STATUS, status)
+            putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
+            putExtra(BatteryManager.EXTRA_SCALE, 100)
+            putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
+            putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
+        }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 29999c2..b472982 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -423,6 +423,7 @@
         "mockito-target-extended-minus-junit4",
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
+        "kotlin-test",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 0f1f168..a892269 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -98,7 +98,6 @@
 yuandizhou@google.com
 yurilin@google.com
 zakcohen@google.com
-zoepage@google.com
 
 #Android TV
 rgl@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 37b1ee5..187d073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -249,7 +249,7 @@
         // intent is to launch a dialog from another dialog.
         val animatedParent =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
+                it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot
             }
         val controller =
             animatedParent?.dialogContentWithBackground?.let {
@@ -336,7 +336,7 @@
     ): ActivityLaunchAnimator.Controller? {
         val animatedDialog =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+                it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
             }
                 ?: return null
         return createActivityLaunchController(animatedDialog, cujType)
@@ -417,7 +417,7 @@
                 animatedDialog.prepareForStackDismiss()
 
                 // Remove the dim.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             }
 
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -783,7 +783,7 @@
         }
 
         // Show the background dim.
-        dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
 
         startAnimation(
             isLaunching = true,
@@ -863,7 +863,7 @@
             isLaunching = false,
             onLaunchAnimationStart = {
                 // Remove the dim background as soon as we start the animation.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             },
             onLaunchAnimationEnd = {
                 val dialogContentWithBackground = this.dialogContentWithBackground!!
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 1a03ede..6c4b695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -206,8 +206,9 @@
             return
         }
 
-        backgroundView = FrameLayout(launchContainer.context)
-        launchContainerOverlay.add(backgroundView)
+        backgroundView = FrameLayout(launchContainer.context).also {
+            launchContainerOverlay.add(it)
+        }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -319,7 +320,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        launchContainerOverlay.remove(backgroundView)
+        backgroundView?.let { launchContainerOverlay.remove(it) }
 
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..d6eba2e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -283,7 +283,7 @@
 
         animator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+                override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation started")
                     }
@@ -295,7 +295,7 @@
                     launchContainerOverlay.add(windowBackgroundLayer)
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index b555fa5..8dc7495 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,7 +42,9 @@
                 return baseTypeface
             }
 
-            val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+            val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+                ?.toMutableList()
+                ?: mutableListOf()
             axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
             if (axes.isEmpty()) {
                 return baseTypeface
@@ -120,8 +122,8 @@
             }
             addListener(
                 object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
-                    override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+                    override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+                    override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
                 }
             )
         }
@@ -302,11 +304,11 @@
             if (onAnimationEnd != null) {
                 val listener =
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             onAnimationEnd.run()
                             animator.removeListener(this)
                         }
-                        override fun onAnimationCancel(animation: Animator?) {
+                        override fun onAnimationCancel(animation: Animator) {
                             animator.removeListener(this)
                         }
                     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 38b99cc..bd3706e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -1046,7 +1046,7 @@
                         }
                     }
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                     }
                 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index c3f44f8..f7ebe2f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -37,7 +37,14 @@
 }
 
 /** Key for a scene. */
-class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
+class SceneKey(
+    name: String,
+    identity: Any = Object(),
+) : Key(name, identity) {
+
+    /** The unique [ElementKey] identifying this scene's root element. */
+    val rootElementKey = ElementKey(name, identity)
+
     override fun toString(): String {
         return "SceneKey(name=$name)"
     }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index 9752f53..f4e3902 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -35,7 +35,7 @@
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions(
-    val transitionSpecs: List<TransitionSpec>,
+    private val transitionSpecs: List<TransitionSpec>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index afd49b4..48d5638e8b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -75,7 +75,7 @@
     }
 }
 
-private class TransitionBuilderImpl : TransitionBuilder {
+internal class TransitionBuilderImpl : TransitionBuilder {
     val transformations = mutableListOf<Transformation>()
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index b9baa793..81b9eb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,7 @@
 import androidx.compose.animation.Crossfade
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -46,6 +46,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -63,6 +64,13 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
+object Bouncer {
+    object Elements {
+        val Background = ElementKey("BouncerBackground")
+        val Content = ElementKey("BouncerContent")
+    }
+}
+
 /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
 @SysUISingleton
 class BouncerScene
@@ -88,7 +96,7 @@
 }
 
 @Composable
-private fun BouncerScene(
+private fun SceneScope.BouncerScene(
     viewModel: BouncerViewModel,
     dialogFactory: BouncerSceneDialogFactory,
     modifier: Modifier = Modifier,
@@ -97,84 +105,90 @@
     val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
+    val backgroundColor = MaterialTheme.colorScheme.surface
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(60.dp),
-        modifier =
-            modifier
-                .fillMaxSize()
-                .background(MaterialTheme.colorScheme.surface)
-                .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
-    ) {
-        Crossfade(
-            targetState = message,
-            label = "Bouncer message",
-            animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-        ) { message ->
-            Text(
-                text = message.text,
-                color = MaterialTheme.colorScheme.onSurface,
-                style = MaterialTheme.typography.bodyLarge,
-            )
+    Box(modifier) {
+        Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
+            drawRect(color = backgroundColor)
         }
 
-        Box(Modifier.weight(1f)) {
-            when (val nonNullViewModel = authMethodViewModel) {
-                is PinBouncerViewModel ->
-                    PinBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PasswordBouncerViewModel ->
-                    PasswordBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PatternBouncerViewModel ->
-                    PatternBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier =
-                            Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
-                                .align(Alignment.BottomCenter),
-                    )
-                else -> Unit
-            }
-        }
-
-        Button(
-            onClick = viewModel::onEmergencyServicesButtonClicked,
-            colors =
-                ButtonDefaults.buttonColors(
-                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                    contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                ),
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(60.dp),
+            modifier =
+                Modifier.element(Bouncer.Elements.Content)
+                    .fillMaxSize()
+                    .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
         ) {
-            Text(
-                text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
+            Crossfade(
+                targetState = message,
+                label = "Bouncer message",
+                animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+            ) { message ->
+                Text(
+                    text = message.text,
+                    color = MaterialTheme.colorScheme.onSurface,
+                    style = MaterialTheme.typography.bodyLarge,
+                )
             }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+
+            Box(Modifier.weight(1f)) {
+                when (val nonNullViewModel = authMethodViewModel) {
+                    is PinBouncerViewModel ->
+                        PinBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PasswordBouncerViewModel ->
+                        PasswordBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PatternBouncerViewModel ->
+                        PatternBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier =
+                                Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+                                    .align(Alignment.BottomCenter),
+                        )
+                    else -> Unit
+                }
+            }
+
+            Button(
+                onClick = viewModel::onEmergencyServicesButtonClicked,
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+            }
+
+            if (dialogMessage != null) {
+                if (dialog == null) {
+                    dialog =
+                        dialogFactory().apply {
+                            setMessage(dialogMessage)
+                            setButton(
+                                DialogInterface.BUTTON_NEUTRAL,
+                                context.getString(R.string.ok),
+                            ) { _, _ ->
+                                viewModel.onThrottlingDialogDismissed()
+                            }
+                            setCancelable(false)
+                            setCanceledOnTouchOutside(false)
+                            show()
+                        }
+                }
+            } else {
+                dialog?.dismiss()
+                dialog = null
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 38b751c..889c026 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -31,9 +31,17 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object Notifications {
+    object Elements {
+        val Notifications = ElementKey("Notifications")
+    }
+}
 
 @Composable
-fun Notifications(
+fun SceneScope.Notifications(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272779828): implement.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 1bb341c..c84a5e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -31,15 +31,27 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object QuickSettings {
+    object Elements {
+        // TODO RENAME
+        val Content = ElementKey("QuickSettingsContent")
+        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+        val FooterActions = ElementKey("QuickSettingsFooterActions")
+    }
+}
 
 @Composable
-fun QuickSettings(
+fun SceneScope.QuickSettings(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272780058): implement.
     Column(
         modifier =
             modifier
+                .element(QuickSettings.Elements.Content)
                 .fillMaxWidth()
                 .defaultMinSize(minHeight = 300.dp)
                 .clip(RoundedCornerShape(32.dp))
@@ -47,15 +59,19 @@
                 .padding(16.dp),
     ) {
         Text(
-            text = "Quick settings",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Quick settings grid",
+            modifier =
+                Modifier.element(QuickSettings.Elements.CollapsedGrid)
+                    .align(Alignment.CenterHorizontally),
             style = MaterialTheme.typography.titleLarge,
             color = MaterialTheme.colorScheme.onPrimary,
         )
         Spacer(modifier = Modifier.weight(1f))
         Text(
             text = "QS footer actions",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
+            modifier =
+                Modifier.element(QuickSettings.Elements.FooterActions)
+                    .align(Alignment.CenterHorizontally),
             style = MaterialTheme.typography.titleSmall,
             color = MaterialTheme.colorScheme.onPrimary,
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 29763c2..e5cd439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,19 +16,17 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
@@ -69,23 +67,18 @@
 }
 
 @Composable
-private fun QuickSettingsScene(
+private fun SceneScope.QuickSettingsScene(
     viewModel: QuickSettingsSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280887232): implement the real UI.
 
-    Box(modifier = modifier) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center)
-        ) {
-            Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
-            }
-        }
+    Box(
+        modifier
+            .fillMaxSize()
+            .clickable(onClick = { viewModel.onContentClicked() })
+            .padding(horizontal = 16.dp, vertical = 48.dp)
+    ) {
+        QuickSettings(modifier = Modifier.fillMaxHeight())
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index f91baf2..c865070 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -31,7 +31,6 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
 import com.android.compose.animation.scene.observableTransitionState
-import com.android.compose.animation.scene.transitions
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
@@ -78,7 +77,7 @@
     SceneTransitionLayout(
         currentScene = currentSceneKey.toTransitionSceneKey(),
         onChangeScene = viewModel::onSceneChanged,
-        transitions = transitions {},
+        transitions = SceneContainerTransitions,
         state = state,
         modifier = modifier.fillMaxSize(),
     ) {
@@ -98,7 +97,9 @@
             ) {
                 with(composableScene) {
                     this@scene.Content(
-                        modifier = Modifier.fillMaxSize(),
+                        modifier =
+                            Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+                                .fillMaxSize(),
                     )
                 }
             }
@@ -129,14 +130,6 @@
 }
 
 // TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
-private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
-    return SceneTransitionSceneKey(
-        name = toString(),
-        identity = this,
-    )
-}
-
-// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
 private fun SceneTransitionSceneKey.toModel(): SceneModel {
     return SceneModel(key = identity as SceneKey)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
new file mode 100644
index 0000000..404bf81
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -0,0 +1,34 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+
+/**
+ * Comprehensive definition of all transitions between scenes in [SceneContainer].
+ *
+ * Transitions are automatically reversible, so define only one transition per scene pair. By
+ * convention, use the more common transition direction when defining the pair order, e.g.
+ * Lockscreen to Bouncer rather than Bouncer to Lockscreen.
+ *
+ * The actual transition DSL must be placed in a separate file under the package
+ * [com.android.systemui.scene.ui.composable.transitions].
+ *
+ * Please keep the list sorted alphabetically.
+ */
+val SceneContainerTransitions = transitions {
+    from(Bouncer, to = Gone) { bouncerToGoneTransition() }
+    from(Gone, to = Shade) { goneToShadeTransition() }
+    from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
+    from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
+    from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+    from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
+    from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
+    from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
new file mode 100644
index 0000000..8d0d705
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.systemui.scene.shared.model.SceneKey
+
+val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
+val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
+val Shade = SceneKey.Shade.toTransitionSceneKey()
+val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
+val Gone = SceneKey.Gone.toTransitionSceneKey()
+
+// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
+fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+    return SceneTransitionSceneKey(name = toString(), identity = this)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
new file mode 100644
index 0000000..1a9face
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Bouncer
+
+fun TransitionBuilder.bouncerToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Bouncer.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
new file mode 100644
index 0000000..38712b0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.goneToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
new file mode 100644
index 0000000..1d57c1a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Shade
+
+fun TransitionBuilder.goneToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Shade.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
new file mode 100644
index 0000000..1fee874
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+fun TransitionBuilder.lockscreenToBouncerTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Bouncer.Elements.Content, y = 300.dp)
+    fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
+    fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
new file mode 100644
index 0000000..da6306d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Lockscreen
+
+fun TransitionBuilder.lockscreenToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
new file mode 100644
index 0000000..9a8a3e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
new file mode 100644
index 0000000..7ecfb62
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+
+fun TransitionBuilder.lockscreenToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+    fractionRange(end = 0.5f) {
+        fade(Shade.Elements.ScrimBackground)
+        translate(
+            QuickSettings.Elements.CollapsedGrid,
+            Edge.Top,
+            startsOutsideLayoutBounds = false,
+        )
+    }
+    fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
new file mode 100644
index 0000000..6c7964b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+
+fun TransitionBuilder.shadeToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Notifications.Elements.Notifications, Edge.Bottom)
+    fade(Notifications.Elements.Notifications)
+    timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ff1cb5f..f985aa2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,16 +16,22 @@
 
 package com.android.systemui.shade.ui.composable
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +50,26 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
+object Shade {
+    object Elements {
+        val QuickSettings = ElementKey("ShadeQuickSettings")
+        val Scrim = ElementKey("ShadeScrim")
+        val ScrimBackground = ElementKey("ShadeScrimBackground")
+    }
+
+    object Dimensions {
+        val ScrimCornerSize = 32.dp
+    }
+
+    object Shapes {
+        val Scrim =
+            RoundedCornerShape(
+                topStart = Dimensions.ScrimCornerSize,
+                topEnd = Dimensions.ScrimCornerSize,
+            )
+    }
+}
+
 /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
 @SysUISingleton
 class ShadeScene
@@ -79,20 +105,28 @@
 }
 
 @Composable
-private fun ShadeScene(
+private fun SceneScope.ShadeScene(
     viewModel: ShadeSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(16.dp),
-        modifier =
-            modifier
-                .fillMaxSize()
-                .clickable(onClick = { viewModel.onContentClicked() })
-                .padding(horizontal = 16.dp, vertical = 48.dp)
-    ) {
-        QuickSettings(modifier = Modifier.height(160.dp))
-        Notifications(modifier = Modifier.weight(1f))
+    Box(modifier.element(Shade.Elements.Scrim)) {
+        Spacer(
+            modifier =
+                Modifier.element(Shade.Elements.ScrimBackground)
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+        )
+
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(16.dp),
+            modifier =
+                Modifier.fillMaxSize()
+                    .clickable(onClick = { viewModel.onContentClicked() })
+                    .padding(horizontal = 16.dp, vertical = 48.dp)
+        ) {
+            QuickSettings(modifier = Modifier.height(160.dp))
+            Notifications(modifier = Modifier.weight(1f))
+        }
     }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 46f5971..92d2bd2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -190,6 +190,9 @@
         /** Flag denoting transit clock are enabled in wallpaper picker. */
         const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
 
+        /** Flag denoting adding apply button to wallpaper picker's grid preview page. */
+        const val FLAG_NAME_GRID_APPLY_BUTTON = "wallpaper_picker_grid_apply_button"
+
         /** Flag denoting whether preview loading animation is enabled. */
         const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION =
             "wallpaper_picker_preview_animation"
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 006fc09..92083b0 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -438,7 +438,6 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -446,7 +445,6 @@
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
--packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
 -packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
 -packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
 -packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index f83fa33..affb76b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -57,7 +57,7 @@
 
         private fun readIntFromBundle(extras: Bundle, key: String): Int? =
             try {
-                extras.getString(key).toInt()
+                extras.getString(key)?.toInt()
             } catch (e: Exception) {
                 null
             }
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area" />
+
+        <com.android.systemui.bouncer.ui.BouncerMessageView
+            android:id="@+id/bouncer_message_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintVertical_chainStyle="packed" />
+
+        <!-- Set this to be just above key1. It would be better to introduce a barrier above
+         key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+         drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+         case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+         fine. -->
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/row0"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+            tools:layout_editor_absoluteX="-16dp">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pin_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+        updated in KeyguardPINView to reduce the height of the PIN pad. -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <com.android.keyguard.KeyguardPinFlowView
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+
+            androidprv:flow_verticalBias="0.5"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pinEntry" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 36f7b96..66c57fc 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -22,6 +22,42 @@
     android:layout_width="match_parent"
     android:outlineProvider="none" >
 
+    <LinearLayout
+        android:id="@id/keyguard_indication_area"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
+        android:layout_gravity="bottom|center_horizontal"
+        android:orientation="vertical">
+
+        <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+            android:id="@id/keyguard_indication_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingStart="@dimen/keyguard_indication_text_padding"
+            android:paddingEnd="@dimen/keyguard_indication_text_padding"
+            android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+            android:accessibilityLiveRegion="polite"/>
+
+        <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+            android:id="@id/keyguard_indication_text_bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:minHeight="@dimen/keyguard_indication_text_min_height"
+            android:layout_gravity="center_horizontal"
+            android:paddingStart="@dimen/keyguard_indication_text_padding"
+            android:paddingEnd="@dimen/keyguard_indication_text_padding"
+            android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:alpha=".8"
+            android:accessibilityLiveRegion="polite"
+            android:visibility="gone"/>
+
+    </LinearLayout>
+
     <com.android.systemui.animation.view.LaunchableImageView
         android:id="@+id/start_button"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e892f7..d85e012 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -21,9 +21,11 @@
     <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
     <!-- padding for container with status icons and battery -->
-    <dimen name="status_bar_icons_padding_end">12dp</dimen>
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
-    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
+    <dimen name="status_bar_icons_padding_bottom">2dp</dimen>
+    <dimen name="status_bar_icons_padding_top">2dp</dimen>
 
     <dimen name="status_bar_padding_end">0dp</dimen>
 
@@ -78,8 +80,8 @@
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
-    <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
-    <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
+    <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d74eca6..dc1f0e4 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,9 +16,6 @@
 */
 -->
 <resources>
-    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
-    <dimen name="status_bar_icons_padding_start">10dp</dimen>
-
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
 
@@ -30,9 +27,6 @@
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
-    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
-    <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
-
     <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
     <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 12b3ec3..5c42e45 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -780,6 +780,9 @@
     <!-- Flag to enable privacy dot views, it shall be true for normal case -->
     <bool name="config_enablePrivacyDot">true</bool>
 
+    <!-- Flag to enable privacy chip animation, it shall be true for normal case -->
+    <bool name="config_enablePrivacyChipAnimation">true</bool>
+
     <!-- Class for the communal source connector to be used -->
     <string name="config_communalSourceConnector" translatable="false"></string>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 572a7fe7..8310b95 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -351,9 +351,9 @@
 
     <!-- paddings for container with status icons and battery -->
     <!-- padding start is a bit smaller than end to account for status icon margin-->
-    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
 
-    <dimen name="status_bar_icons_padding_end">0dp</dimen>
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
     <dimen name="status_bar_icons_padding_bottom">0dp</dimen>
     <dimen name="status_bar_icons_padding_top">0dp</dimen>
 
@@ -364,7 +364,7 @@
     <dimen name="status_bar_padding_start">8dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
-    <dimen name="status_bar_padding_end">8dp</dimen>
+    <dimen name="status_bar_padding_end">4dp</dimen>
 
     <!-- the padding on the top of the statusbar (usually 0) -->
     <dimen name="status_bar_padding_top">0dp</dimen>
@@ -1606,7 +1606,7 @@
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
     <!-- below also works as break between user chip and hover state of status icons -->
-    <dimen name="status_bar_user_chip_end_margin">4dp</dimen>
+    <dimen name="status_bar_user_chip_end_margin">8dp</dimen>
     <dimen name="status_bar_user_chip_text_size">12sp</dimen>
 
     <!-- System UI Dialog -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c2dba6c..261b08d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -40,4 +40,7 @@
 
     <!-- Whether face auth will immediately stop when the display state is OFF -->
     <bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+    <!-- Whether we want to stop pulsing while running the face scanning animation -->
+    <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0befb3b..cddfda2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -747,6 +747,9 @@
     off. This means a separate profile on a user's phone that's specifically for their
     work apps and managed by their company. "Work" is used as an adjective. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_work_mode_label">Work apps</string>
+    <!-- QuickSettings: Subtitle for the Work profile Quick Settings tile when it's in the off
+    state. This corresponds to the work profile not being currently accessible. [CHAR LIMIT=20] -->
+    <string name="quick_settings_work_mode_paused_state">Paused</string>
     <!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
     <string name="quick_settings_night_display_label">Night Light</string>
     <!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
@@ -2403,6 +2406,8 @@
     <string name="magnification_open_settings_click_label">Open magnification settings</string>
     <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
     <string name="magnification_close_settings_click_label">Close magnification settings</string>
+    <!-- Click action label for exiting magnifier edit mode. [CHAR LIMIT=NONE] -->
+    <string name="magnification_exit_edit_mode_click_label">Exit edit mode</string>
     <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
     <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cb2c3a1..2ec6180 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -60,7 +60,7 @@
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/privacy_container"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+            app:layout_constraintStart_toEndOf="@id/carrier_group"/>
         <PropertySet android:alpha="1" />
     </Constraint>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index c142933..5edd283 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -27,6 +27,6 @@
      */
     fun ActivityManager.isInForeground(packageName: String): Boolean {
         val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
-        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+        return tasks.isNotEmpty() && packageName == tasks[0].topActivity?.packageName
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index d7e61d6..ebc57d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -31,15 +31,15 @@
     var visibleOnScreen = false
 
     constructor(parcel: Parcel) : this() {
-        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) ?: Rect()
         this.selectedPage = parcel.readInt()
         this.visibleOnScreen = parcel.readBoolean()
     }
 
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeParcelable(boundsOnScreen, 0)
-        dest?.writeInt(selectedPage)
-        dest?.writeBoolean(visibleOnScreen)
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(boundsOnScreen, 0)
+        dest.writeInt(selectedPage)
+        dest.writeBoolean(visibleOnScreen)
     }
 
     override fun describeContents(): Int {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index aca9907..dac130d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -39,7 +39,7 @@
 
     fun init() {
         rotationChangeProvider.addCallback(rotationListener)
-        rotationListener.onRotationChanged(context.display.rotation)
+        context.display?.rotation?.let { rotationListener.onRotationChanged(it) }
     }
 
     private val rotationListener = RotationListener { rotation ->
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 78a5c98..495367b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -105,7 +105,7 @@
 
         hideAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     super@BouncerKeyguardMessageArea.setMessage(msg, animate)
                 }
             }
@@ -118,7 +118,7 @@
 
         showAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     textAboutToShow = null
                 }
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0b9e6e9..2f3c1f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -144,7 +144,7 @@
                 smallClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
                         var pastVisibility: Int? = null
-                        override fun onViewAttachedToWindow(view: View?) {
+                        override fun onViewAttachedToWindow(view: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                             if (view != null) {
                                 smallClockFrame = view.parent as FrameLayout
@@ -168,7 +168,7 @@
                             }
                         }
 
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
                             smallClockFrame?.viewTreeObserver
                                     ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
                         }
@@ -178,10 +178,10 @@
 
                 largeClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View?) {
+                        override fun onViewAttachedToWindow(p0: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                         }
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
                         }
                 }
                 value.largeClock.view
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 61addab..3f3efe9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -666,6 +667,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
         if (mViewMediatorCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4e1cbc7..d9a1dc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,7 +33,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.media.AudioManager;
@@ -69,6 +68,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -78,11 +78,10 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneKey;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -128,6 +127,7 @@
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
     // the audio service will bring up the volume dialog.
@@ -143,7 +143,7 @@
     private Runnable mCancelAction;
     private boolean mWillRunDismissFromKeyguard;
 
-    private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mLastOrientation;
 
     private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
     private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
@@ -301,6 +301,10 @@
                     mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
                 }
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+            }
         }
 
         @Override
@@ -349,7 +353,14 @@
 
                 @Override
                 public void onDensityOrFontScaleChanged() {
-                    KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
+                }
+
+                @Override
+                public void onOrientationChanged(int orientation) {
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
                 }
             };
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -388,7 +399,7 @@
                 }
             };
     private final UserInteractor mUserInteractor;
-    private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     @Nullable private Job mSceneTransitionCollectionJob;
 
@@ -419,7 +430,8 @@
             Provider<JavaAdapter> javaAdapter,
             UserInteractor userInteractor,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
-            Provider<SceneInteractor> sceneInteractor
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Provider<AuthenticationInteractor> authenticationInteractor
     ) {
         super(view);
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,8 +460,9 @@
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
         mUserInteractor = userInteractor;
-        mSceneInteractor = sceneInteractor;
+        mAuthenticationInteractor = authenticationInteractor;
         mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -474,19 +487,21 @@
         showPrimarySecurityScreen(false);
 
         if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-            // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+            // When the scene framework says that the lockscreen has been dismissed, dismiss the
+            // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mSceneInteractor.get().finishedSceneTransitions(
-                    /* from= */ SceneKey.Bouncer.INSTANCE,
-                    /* to= */ SceneKey.Gone.INSTANCE),
-                unused -> {
-                    final int selectedUserId = mUserInteractor.getSelectedUserId();
-                    showNextSecurityScreenOrFinish(
+                mAuthenticationInteractor.get().isLockscreenDismissed(),
+                isLockscreenDismissed -> {
+                    if (isLockscreenDismissed) {
+                        final int selectedUserId = mUserInteractor.getSelectedUserId();
+                        showNextSecurityScreenOrFinish(
                             /* authenticated= */ true,
                             selectedUserId,
                             /* bypassSecondaryLockScreen= */ true,
                             mSecurityModel.getSecurityMode(selectedUserId));
-                });
+                    }
+                }
+            );
         }
     }
 
@@ -833,8 +848,7 @@
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
                     boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
                             KeyguardUpdateMonitor.getCurrentUser());
-
-                    if (securityMode == SecurityMode.None) {
+                    if (securityMode == SecurityMode.None || isLockscreenDisabled) {
                         finish = isLockscreenDisabled;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
@@ -1154,7 +1168,7 @@
     }
 
     /** Handles density or font scale changes. */
-    private void onDensityOrFontScaleChanged() {
+    private void onDensityOrFontScaleOrOrientationChanged() {
         reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 96ac8ad..e1c060f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -66,11 +66,11 @@
     }
 
     override fun createAnimator(
-        sceneRoot: ViewGroup?,
+        sceneRoot: ViewGroup,
         startValues: TransitionValues?,
         endValues: TransitionValues?
     ): Animator? {
-        if (sceneRoot == null || startValues == null || endValues == null) {
+        if (startValues == null || endValues == null) {
             return null
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 7585279..5774e42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@
 import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CrossFadeHelper;
 
 import java.io.PrintWriter;
@@ -110,6 +112,11 @@
         }
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 73b4c5f..757022d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -543,7 +543,8 @@
 
         @Nullable
         @Override
-        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                @Nullable TransitionValues startValues,
                 @Nullable TransitionValues endValues) {
             if (startValues == null || endValues == null) {
                 return null;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 1a0c7f9..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,6 +22,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -154,11 +155,16 @@
     }
 
     float getLocationTop() {
-        return mLockIconCenter.y - mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.top;
     }
 
     float getLocationBottom() {
-        return mLockIconCenter.y + mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.bottom;
+
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index a04a48d..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -58,6 +58,7 @@
     private float mStartRadius;
     private float mEndRadius;
     private int mHeight;
+    private int mWidth;
 
     private static final int EXPAND_ANIMATION_MS = 100;
     private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
         mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
         int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
         int difference = mHeight - height;
-        mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+        int left = 0;
+        int top = difference / 2;
+        int right = mWidth;
+        int bottom = mHeight - difference / 2;
+        mBackground.setBounds(left, top, right, bottom);
     }
 
-    void onLayout(int height) {
+    void onLayout(int width, int height) {
         boolean shouldUpdateHeight = height != mHeight;
+        mWidth = width;
         mHeight = height;
         mStartRadius = height / 2f;
         mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
         ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
         @SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
         mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
-                       NUM_PAD_BACKGROUND);
+                NUM_PAD_BACKGROUND);
         a.recycle();
 
         mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3f1741a6..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -74,8 +74,9 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index edc298c..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -211,7 +211,9 @@
         left = centerX - mKlondikeText.getMeasuredWidth() / 2;
         mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
 
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b01e136..0180384 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -130,14 +130,14 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
-import dagger.Lazy;
-
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import dagger.Lazy;
+
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
  * long-term dependency injection solution.
@@ -278,7 +278,6 @@
     @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
     @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
     @Inject Lazy<TunablePaddingService> mTunablePaddingService;
-    @Inject Lazy<ForegroundServiceController> mForegroundServiceController;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
     @Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
     @Inject Lazy<LightBarController> mLightBarController;
@@ -456,8 +455,6 @@
 
         mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
 
-        mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
-
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
 
         mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c809..95e2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@
     val mainExecutor: Executor,
     val logger: ScreenDecorationsLogger,
     val authController: AuthController,
+    val featureFlags: FeatureFlags,
 ) : ScreenDecorations.DisplayCutoutView(context, pos) {
     private var showScanningAnim = false
     private val rimPaint = Paint()
@@ -294,6 +297,15 @@
     }
 
     private fun createFaceScanningRimAnimator(): AnimatorSet {
+        val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+        if (dontPulse) {
+            return AnimatorSet().apply {
+                playSequentially(
+                        cameraProtectionAnimator,
+                        createRimAppearAnimator(),
+                )
+            }
+        }
         return AnimatorSet().apply {
             playSequentially(
                 cameraProtectionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
deleted file mode 100644
index 15e8c4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ /dev/null
@@ -1,183 +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;
-
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
-
-import javax.inject.Inject;
-
-/**
- * Tracks state of foreground services and notifications related to foreground services per user.
- */
-@SysUISingleton
-public class ForegroundServiceController {
-    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
-
-    private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
-    private final Object mMutex = new Object();
-    private final Handler mMainHandler;
-
-    @Inject
-    public ForegroundServiceController(
-            AppOpsController appOpsController,
-            @Main Handler mainHandler) {
-        mMainHandler = mainHandler;
-        appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
-            mMainHandler.post(() -> {
-                onAppOpChanged(code, uid, packageName, active);
-            });
-        });
-    }
-
-    /**
-     * @return true if this user has services missing notifications and therefore needs a
-     * disclosure notification for running a foreground service.
-     */
-    public boolean isDisclosureNeededForUser(int userId) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.isDisclosureNeeded();
-        }
-    }
-
-    /**
-     * @return true if this user/pkg has a missing or custom layout notification and therefore needs
-     * a disclosure notification showing the user which appsOps the app is using.
-     */
-    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.getStandardLayoutKeys(pkg) == null;
-        }
-    }
-
-    /**
-     * Gets active app ops for this user and package
-     */
-    @Nullable
-    public ArraySet<Integer> getAppOps(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) {
-                return null;
-            }
-            return services.getFeatures(pkg);
-        }
-    }
-
-    /**
-     * Records active app ops and updates the app op for the pending or visible notifications
-     * with the given parameters.
-     * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
-     * have a notification to tag.
-     * @param appOpCode code for appOp to add/remove
-     * @param uid of user the notification is sent to
-     * @param packageName package that created the notification
-     * @param active whether the appOpCode is active or not
-     */
-    void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
-        Assert.isMainThread();
-
-        int userId = UserHandle.getUserId(uid);
-        // Record active app ops
-        synchronized (mMutex) {
-            ForegroundServicesUserState userServices = mUserServices.get(userId);
-            if (userServices == null) {
-                userServices = new ForegroundServicesUserState();
-                mUserServices.put(userId, userServices);
-            }
-            if (active) {
-                userServices.addOp(packageName, appOpCode);
-            } else {
-                userServices.removeOp(packageName, appOpCode);
-            }
-        }
-    }
-
-    /**
-     * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
-     * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
-     * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
-     * If {@code createIfNotFound} is false, no update is performed.
-     *
-     * @return false if no user state was found and none was created; true otherwise.
-     */
-    boolean updateUserState(int userId,
-            UserStateUpdateCallback updateCallback,
-            boolean createIfNotFound) {
-        synchronized (mMutex) {
-            ForegroundServicesUserState userState = mUserServices.get(userId);
-            if (userState == null) {
-                if (createIfNotFound) {
-                    userState = new ForegroundServicesUserState();
-                    mUserServices.put(userId, userState);
-                } else {
-                    return false;
-                }
-            }
-            return updateCallback.updateUserState(userState);
-        }
-    }
-
-    /**
-     * @return true if {@code sbn} is the system-provided disclosure notification containing the
-     * list of running foreground services.
-     */
-    public boolean isDisclosureNotification(StatusBarNotification sbn) {
-        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
-                && sbn.getTag() == null
-                && sbn.getPackageName().equals("android");
-    }
-
-    /**
-     * @return true if sbn is one of the window manager "drawing over other apps" notifications
-     */
-    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
-        return sbn.getPackageName().equals("android")
-                && sbn.getTag() != null
-                && sbn.getTag().contains("AlertWindowNotification");
-    }
-
-    /**
-     * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
-     * to perform the update.
-     */
-    interface UserStateUpdateCallback {
-        /**
-         * Perform update operations on the provided {@code userState}.
-         *
-         * @return true if the update succeeded.
-         */
-        boolean updateUserState(ForegroundServicesUserState userState);
-
-        /** Called if the state was not found and was not created. */
-        default void userStateNotFound(int userId) {
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
deleted file mode 100644
index a1a3b72..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ /dev/null
@@ -1,150 +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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import javax.inject.Inject;
-
-/** Updates foreground service notification state in response to notification data events. */
-@SysUISingleton
-public class ForegroundServiceNotificationListener {
-
-    private static final String TAG = "FgServiceController";
-    private static final boolean DBG = false;
-
-    private final Context mContext;
-    private final ForegroundServiceController mForegroundServiceController;
-    private final NotifPipeline mNotifPipeline;
-
-    @Inject
-    public ForegroundServiceNotificationListener(Context context,
-            ForegroundServiceController foregroundServiceController,
-            NotifPipeline notifPipeline) {
-        mContext = context;
-        mForegroundServiceController = foregroundServiceController;
-        mNotifPipeline = notifPipeline;
-    }
-
-    /** Initializes this listener by connecting it to the notification pipeline. */
-    public void init() {
-        mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryAdded(NotificationEntry entry) {
-                addNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryUpdated(NotificationEntry entry) {
-                updateNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryRemoved(NotificationEntry entry, int reason) {
-                removeNotification(entry.getSbn());
-            }
-        });
-    }
-
-    /**
-     * @param entry notification that was just posted
-     */
-    private void addNotification(NotificationEntry entry, int importance) {
-        updateNotification(entry, importance);
-    }
-
-    /**
-     * @param sbn notification that was just removed
-     */
-    private void removeNotification(StatusBarNotification sbn) {
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                new ForegroundServiceController.UserStateUpdateCallback() {
-                    @Override
-                    public boolean updateUserState(ForegroundServicesUserState userState) {
-                        if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                            // if you remove the dungeon entirely, we take that to mean there are
-                            // no running services
-                            userState.setRunningServices(null, 0);
-                            return true;
-                        } else {
-                            // this is safe to call on any notification, not just
-                            // FLAG_FOREGROUND_SERVICE
-                            return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-
-                    @Override
-                    public void userStateNotFound(int userId) {
-                        if (DBG) {
-                            Log.w(TAG, String.format(
-                                    "user %d with no known notifications got removeNotification "
-                                            + "for %s",
-                                    sbn.getUserId(), sbn));
-                        }
-                    }
-                },
-                false /* don't create */);
-    }
-
-    /**
-     * @param entry notification that was just changed in some way
-     */
-    private void updateNotification(NotificationEntry entry, int newImportance) {
-        final StatusBarNotification sbn = entry.getSbn();
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                userState -> {
-                    if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                        final Bundle extras = sbn.getNotification().extras;
-                        if (extras != null) {
-                            final String[] svcs = extras.getStringArray(
-                                    Notification.EXTRA_FOREGROUND_APPS);
-                            userState.setRunningServices(svcs, sbn.getNotification().when);
-                        }
-                    } else {
-                        userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        if (0 != (sbn.getNotification().flags
-                                & Notification.FLAG_FOREGROUND_SERVICE)) {
-                            if (newImportance > NotificationManager.IMPORTANCE_MIN) {
-                                userState.addImportantNotification(sbn.getPackageName(),
-                                        sbn.getKey());
-                            }
-                        }
-                        final Notification.Builder builder =
-                                Notification.Builder.recoverBuilder(
-                                        mContext, sbn.getNotification());
-                        if (builder.usesStandardHeader()) {
-                            userState.addStandardLayoutNotification(
-                                    sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-                    return true;
-                },
-                true /* create if not found */);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 6ea0fc3..b33d501 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -80,7 +80,6 @@
 
     @Override
     public void start() {
-        updateEnabled();
     }
 
     private void fakeWakeAndUnlock(BiometricSourceType type) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 4a31f3d..f1cebba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1476,6 +1476,12 @@
     private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
 
         private CharSequence getClickAccessibilityActionLabel() {
+            if (mEditSizeEnable) {
+                // Perform click action to exit edit mode
+                return mContext.getResources().getString(
+                        R.string.magnification_exit_edit_mode_click_label);
+            }
+
             return mSettingsPanelVisibility
                     ? mContext.getResources().getString(
                             R.string.magnification_close_settings_click_label)
@@ -1518,8 +1524,14 @@
 
         private boolean performA11yAction(int action) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                // Simulate tapping the drag view so it opens the Settings.
-                handleSingleTap(mDragView);
+                if (mEditSizeEnable) {
+                    // When edit mode is enabled, click the magnifier to exit edit mode.
+                    setEditMagnifierSizeMode(false);
+                } else {
+                    // Simulate tapping the drag view so it opens the Settings.
+                    handleSingleTap(mDragView);
+                }
+
             } else if (action == R.id.accessibility_action_zoom_in) {
                 performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_zoom_out) {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 9708d9a..c28a9eb 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -20,6 +20,7 @@
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
 
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -33,6 +34,7 @@
 import android.permission.PermissionManager;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import androidx.annotation.WorkerThread;
@@ -96,19 +98,58 @@
     private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
             new SparseArray<>();
 
-    protected static final int[] OPS = new int[] {
-            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
-            AppOpsManager.OP_CAMERA,
-            AppOpsManager.OP_PHONE_CALL_CAMERA,
-            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+    @VisibleForTesting
+    protected static final int[] OPS_MIC = new int[] {
             AppOpsManager.OP_RECORD_AUDIO,
-            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
-            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
             AppOpsManager.OP_PHONE_CALL_MICROPHONE,
-            AppOpsManager.OP_COARSE_LOCATION,
-            AppOpsManager.OP_FINE_LOCATION
+            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
     };
 
+    protected static final int[] OPS_CAMERA = new int[] {
+            AppOpsManager.OP_CAMERA,
+            AppOpsManager.OP_PHONE_CALL_CAMERA
+    };
+
+    protected static final int[] OPS_LOC = new int[] {
+            AppOpsManager.OP_FINE_LOCATION,
+            AppOpsManager.OP_COARSE_LOCATION,
+            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
+    };
+
+    protected static final int[] OPS_OTHERS = new int[] {
+            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO
+    };
+
+
+   protected static final int[] OPS = concatOps(OPS_MIC, OPS_CAMERA, OPS_LOC, OPS_OTHERS);
+
+    /**
+     * @param opArrays the given op arrays.
+     * @return the concatenations of the given op arrays. Null arrays are treated as empty.
+     */
+    private static int[] concatOps(@Nullable int[]...opArrays) {
+        if (opArrays == null) {
+            return new int[0];
+        }
+        int totalLength = 0;
+        for (int[] opArray : opArrays) {
+            if (opArray == null || opArray.length == 0) {
+                continue;
+            }
+            totalLength += opArray.length;
+        }
+        final int[] concatOps = new int[totalLength];
+        int index = 0;
+        for (int[] opArray : opArrays) {
+            if (opArray == null || opArray.length == 0) continue;
+            System.arraycopy(opArray, 0, concatOps, index, opArray.length);
+            index += opArray.length;
+        }
+        return concatOps;
+    }
+
     @Inject
     public AppOpsControllerImpl(
             Context context,
@@ -533,12 +574,17 @@
     }
 
     private boolean isOpCamera(int op) {
-        return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
+        for (int i = 0; i < OPS_CAMERA.length; i++) {
+            if (op == OPS_CAMERA[i]) return true;
+        }
+        return false;
     }
 
     private boolean isOpMicrophone(int op) {
-        return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE
-                || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            if (op == OPS_MIC[i]) return true;
+        }
+        return false;
     }
 
     protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index e121790..ecd7bae 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -102,7 +102,7 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = true,
+                initialValue = false,
             )
 
     /**
@@ -114,14 +114,18 @@
      * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
      *   transition occurs).
      */
-    private val isLockscreenDismissed =
+    val isLockscreenDismissed: StateFlow<Boolean> =
         sceneInteractor.desiredScene
             .map { it.key }
             .filter { currentScene ->
                 currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
             }
             .map { it == SceneKey.Gone }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
     /**
      * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 58adfa1..58c8000 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,6 +82,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.PrintWriter;
@@ -288,12 +289,13 @@
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
                 promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
-                new Handler(Looper.getMainLooper()), bgExecutor);
+                new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper);
     }
 
     @VisibleForTesting
@@ -314,7 +316,8 @@
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         super(config.mContext);
 
         mConfig = config;
@@ -364,7 +367,8 @@
         if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
             showPrompt(config, layoutInflater, promptViewModel,
                     Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
-                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+                    vibratorHelper, featureFlags);
         } else {
             showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
         }
@@ -388,7 +392,10 @@
     private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
             @NonNull PromptViewModel viewModel,
             @Nullable FingerprintSensorPropertiesInternal fpProps,
-            @Nullable FaceSensorPropertiesInternal faceProps) {
+            @Nullable FaceSensorPropertiesInternal faceProps,
+            @NonNull VibratorHelper vibratorHelper,
+            @NonNull FeatureFlags featureFlags
+    ) {
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
                     config.mPromptInfo,
@@ -401,7 +408,8 @@
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
                     getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                    vibratorHelper, featureFlags);
 
             // TODO(b/251476085): migrate these dependencies
             if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 3df7ca5..7b288a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,9 +85,12 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.data.repository.BiometricType;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -101,7 +104,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import kotlin.Unit;
 import kotlinx.coroutines.CoroutineScope;
 
 /**
@@ -183,6 +185,7 @@
     @NonNull private final UdfpsUtils mUdfpsUtils;
     private final @Background DelayableExecutor mBackgroundExecutor;
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+    @NonNull private final VibratorHelper mVibratorHelper;
 
     @VisibleForTesting
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
@@ -771,7 +774,8 @@
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
             @Background DelayableExecutor bgExecutor,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull VibratorHelper vibratorHelper) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mExecution = execution;
@@ -794,6 +798,7 @@
         mFaceEnrolledForUser = new SparseBooleanArray();
         mUdfpsUtils = udfpsUtils;
         mApplicationCoroutineScope = applicationCoroutineScope;
+        mVibratorHelper = vibratorHelper;
 
         mLogContextInteractor = logContextInteractor;
         mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
@@ -1044,7 +1049,7 @@
         final int userId = mCurrentDialogArgs.argi1;
         if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
             messageRes = modality == TYPE_FACE
-                    ? R.string.biometric_face_not_recognized
+                    ? R.string.fingerprint_dialog_use_fingerprint_instead
                     : R.string.fingerprint_error_not_match;
         } else {
             messageRes = R.string.biometric_not_recognized;
@@ -1341,7 +1346,7 @@
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
                 mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
-                mCredentialViewModelProvider, bgExecutor);
+                mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 946ddba..ea9fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -230,7 +230,7 @@
                         lightRevealScrim.revealAmount = animator.animatedValue as Float
                     }
                     addListener(object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             // Reset light reveal scrim to the default, so the CentralSurfaces
                             // can handle any subsequent light reveal changes
                             // (ie: from dozing changes)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..4c2dc41 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -147,12 +147,12 @@
             retractDwellAnimator = AnimatorSet().apply {
                 playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -182,13 +182,13 @@
                     invalidate()
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         retractDwellAnimator?.cancel()
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -239,14 +239,14 @@
                     expandDwellRippleAnimator
             )
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     retractDwellAnimator?.cancel()
                     fadeDwellAnimator?.cancel()
                     visibility = VISIBLE
                     drawDwell = true
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     drawDwell = false
                 }
             })
@@ -273,12 +273,12 @@
 
         unlockedRippleAnimator = rippleAnimator.apply {
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     drawRipple = true
                     visibility = VISIBLE
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     onAnimationEnd?.run()
                     drawRipple = false
                     visibility = GONE
@@ -327,7 +327,7 @@
         }
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
         // the active effect area. Values here should be kept in sync with the
         // animation implementation in the ripple shader. (Twice bigger)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index b9fa240..a24a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -40,7 +40,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val faceAuthInteractor: KeyguardFaceAuthInteractor,
 ) : View.AccessibilityDelegate() {
-    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
         if (keyguardUpdateMonitor.shouldListenForFace()) {
             val clickActionToRetryFace =
@@ -52,7 +52,7 @@
         }
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
             keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
             faceAuthInteractor.onAccessibilityAction()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 869d084..0d7d9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -119,7 +119,7 @@
     private var overlayView: View? = null
         set(value) {
             field?.let { oldView ->
-                val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+                val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
                 lottie.pauseAnimation()
                 windowManager.removeView(oldView)
                 orientationListener.disable()
@@ -274,7 +274,7 @@
             }
         overlayOffsets = offsets
 
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        val lottie = view.requireViewById(R.id.sidefps_animation) as LottieAnimationView
         view.rotation =
             display.asSideFpsAnimationRotation(
                 offsets.isYAligned(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 39a45f7..b23e085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -646,8 +646,9 @@
             shouldPilfer = true;
         }
 
-        // Pilfer only once per gesture
-        if (shouldPilfer && !mPointerPilfered) {
+        // Pilfer only once per gesture, don't pilfer for BP
+        if (shouldPilfer && !mPointerPilfered
+                && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
             mPointerPilfered = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index 8352d0a..5dafa61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -38,7 +38,8 @@
     override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
 
     fun updateAccessibilityViewLocation(sensorBounds: Rect) {
-        val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+        val fingerprintAccessibilityView: View =
+            requireViewById(R.id.udfps_enroll_accessibility_view)
         val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
         params.width = sensorBounds.width()
         params.height = sensorBounds.height()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
index fb7b56e..8497879 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -33,7 +33,7 @@
     @Main private val resources: Resources,
     private val keyguardViewManager: StatusBarKeyguardViewManager,
 ) : View.AccessibilityDelegate() {
-    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
         val clickAction =
             AccessibilityNodeInfo.AccessibilityAction(
@@ -43,7 +43,7 @@
         info.addAction(clickAction)
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         // when an a11y service is enabled, double tapping on the fingerprint sensor should
         // show the primary bouncer
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index db30a55..e3fd3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -306,8 +306,9 @@
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
         view.mUseExpandedOverlay = useExpandedOverlay
         view.startIconAsyncInflate {
-            (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
-                udfpsKeyguardAccessibilityDelegate
+            val animationViewInternal: View =
+                view.requireViewById(R.id.udfps_animation_view_internal)
+            animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085..1ca57e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@
         return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
     }
 
+    /**
+     * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+     * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+     * (context: b/281765653#comment18)
+     * Using {@link View#announceForAccessibility} instead as workaround when sending events
+     * exceeding this frequency is required.
+     */
     @JvmStatic
     fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
         if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 1f1a1b5..2a02667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -89,7 +89,7 @@
                 )
             val hat = gkResponse.gatekeeperHAT
             lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
-            emit(CredentialStatus.Success.Verified(hat))
+            emit(CredentialStatus.Success.Verified(checkNotNull(hat)))
         } else if (response.timeout > 0) {
             // if requests are being throttled, update the error message every
             // second until the temporary lock has expired
@@ -226,8 +226,7 @@
             is BiometricPromptRequest.Credential.Password ->
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+    val getFallbackString = {
         val defaultId =
             when (request) {
                 is BiometricPromptRequest.Credential.Pin ->
@@ -239,6 +238,8 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
 
 private fun Context.getLastAttemptBeforeWipeUserMessage(
@@ -266,8 +267,8 @@
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
             else -> DevicePolicyResources.UNDEFINED
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+
+    val getFallbackString = {
         val defaultId =
             when (userType) {
                 UserType.PRIMARY ->
@@ -279,4 +280,6 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index eca0ada..709fe85 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -121,7 +121,7 @@
                 titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
                 titleView.marqueeRepeatLimit = -1
                 // select to enable marquee unless a screen reader is enabled
-                titleView.isSelected = accessibilityManager.shouldMarquee()
+                titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false
             } else {
                 titleView.isSingleLine = false
                 titleView.ellipsize = null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b78761..d054751 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.binder
 
 import android.animation.Animator
+import android.annotation.SuppressLint
 import android.content.Context
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
@@ -25,6 +26,8 @@
 import android.os.Bundle
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import android.view.View
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
@@ -45,7 +48,6 @@
 import com.android.systemui.biometrics.AuthBiometricViewAdapter
 import com.android.systemui.biometrics.AuthIconController
 import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
@@ -55,9 +57,13 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
@@ -69,6 +75,7 @@
 object BiometricViewBinder {
 
     /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+    @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
         view: BiometricPromptLayout,
@@ -78,20 +85,19 @@
         backgroundView: View,
         legacyCallback: Callback,
         applicationScope: CoroutineScope,
+        vibratorHelper: VibratorHelper,
+        featureFlags: FeatureFlags,
     ): AuthBiometricViewAdapter {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
-        fun notifyAccessibilityChanged() {
-            Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
-        }
 
         val textColorError =
             view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
         val textColorHint =
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
-        val titleView = view.findViewById<TextView>(R.id.title)
-        val subtitleView = view.findViewById<TextView>(R.id.subtitle)
-        val descriptionView = view.findViewById<TextView>(R.id.description)
+        val titleView = view.requireViewById<TextView>(R.id.title)
+        val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
+        val descriptionView = view.requireViewById<TextView>(R.id.description)
 
         // set selected to enable marquee unless a screen reader is enabled
         titleView.isSelected =
@@ -100,18 +106,18 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
-        val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
-        val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+        val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
         // Negative-side (left) buttons
-        val negativeButton = view.findViewById<Button>(R.id.button_negative)
-        val cancelButton = view.findViewById<Button>(R.id.button_cancel)
-        val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+        val negativeButton = view.requireViewById<Button>(R.id.button_negative)
+        val cancelButton = view.requireViewById<Button>(R.id.button_cancel)
+        val credentialFallbackButton = view.requireViewById<Button>(R.id.button_use_credential)
 
         // Positive-side (right) buttons
-        val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
-        val retryButton = view.findViewById<Button>(R.id.button_try_again)
+        val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
+        val retryButton = view.requireViewById<Button>(R.id.button_try_again)
 
         // TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
         val adapter =
@@ -297,21 +303,19 @@
 
                 // reuse the icon as a confirm button
                 launch {
-                    viewModel.isConfirmButtonVisible
+                    viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
                                 isPending && iconController.actsAsConfirmButton ->
-                                    View.OnClickListener { viewModel.confirmAuthenticated() }
+                                    View.OnTouchListener { _: View, event: MotionEvent ->
+                                        viewModel.onOverlayTouch(event)
+                                    }
                                 else -> null
                             }
                         }
-                        .collect { onClick ->
-                            iconViewOverlay.setOnClickListener(onClick)
-                            iconView.setOnClickListener(onClick)
-                            if (onClick == null) {
-                                iconViewOverlay.isClickable = false
-                                iconView.isClickable = false
-                            }
+                        .collect { onTouch ->
+                            iconViewOverlay.setOnTouchListener(onTouch)
+                            iconView.setOnTouchListener(onTouch)
                         }
                 }
 
@@ -326,30 +330,30 @@
                     }
                 }
 
-                // not sure why this is here, but the legacy code did it probably needed?
-                launch {
-                    viewModel.isAuthenticating.collect { isAuthenticating ->
-                        if (isAuthenticating) {
-                            notifyAccessibilityChanged()
-                        }
-                    }
-                }
-
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
                         // Disable background view for cancelling authentication once authenticated,
                         // and remove from talkback
                         if (authState.isAuthenticated) {
+                            // Prevents Talkback from speaking subtitle after already authenticated
+                            subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
                             backgroundView.setOnClickListener(null)
                             backgroundView.importantForAccessibility =
                                 IMPORTANT_FOR_ACCESSIBILITY_NO
+
+                            // Allow icon to be used as confirmation button with a11y enabled
+                            if (accessibilityManager.isTouchExplorationEnabled) {
+                                iconViewOverlay.setOnClickListener {
+                                    viewModel.confirmAuthenticated()
+                                }
+                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+                            }
                         }
                         if (authState.isAuthenticatedAndConfirmed) {
                             view.announceForAccessibility(
                                 view.resources.getString(R.string.biometric_dialog_authenticated)
                             )
-                            notifyAccessibilityChanged()
 
                             launch {
                                 delay(authState.delay)
@@ -381,7 +385,30 @@
                             !accessibilityManager.isEnabled ||
                                 !accessibilityManager.isTouchExplorationEnabled
 
-                        notifyAccessibilityChanged()
+                        /**
+                         * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+                         * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+                         * b/281765653#comment18) Using {@link View#announceForAccessibility}
+                         * instead as workaround since sending events exceeding this frequency is
+                         * required.
+                         */
+                        indicatorMessageView?.text?.let {
+                            if (it.isNotBlank()) {
+                                view.announceForAccessibility(it)
+                            }
+                        }
+                    }
+                }
+
+                // Play haptics
+                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    launch {
+                        viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+                            if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+                                vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+                                viewModel.clearHaptics()
+                            }
+                        }
                     }
                 }
             }
@@ -477,7 +504,7 @@
             modalities.hasFaceAndFingerprint &&
                 (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
                 (authenticatedModality == BiometricModality.Face) ->
-                R.string.biometric_dialog_tap_confirm_with_face
+                R.string.biometric_dialog_tap_confirm_with_face_alt_1
             else -> null
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a286cf..370b36b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -72,7 +72,7 @@
             }
         }
 
-        val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
         val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
         val fullSizeYOffset =
             view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
@@ -205,7 +205,7 @@
 }
 
 private fun View.isLandscape(): Boolean {
-    val r = context.display.rotation
+    val r = context.display?.rotation
     return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index dca19c5..89561a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,11 +17,15 @@
 
 import android.hardware.biometrics.BiometricPrompt
 import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -43,6 +47,7 @@
 constructor(
     private val interactor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
+    private val featureFlags: FeatureFlags,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -63,11 +68,18 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
+    private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     /**
      * If the API caller or the user's personal preferences require explicit confirmation after
      * successful authentication.
      */
-    val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
+    val isConfirmationRequired: Flow<Boolean> =
+        combine(_isOverlayTouched, interactor.isConfirmationRequired) {
+            isOverlayTouched,
+            isConfirmationRequired ->
+            !isOverlayTouched && isConfirmationRequired
+        }
 
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -90,6 +102,11 @@
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
+    private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+
+    /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+    val hapticsToPlay = _hapticsToPlay.asStateFlow()
+
     /** The size of the prompt. */
     val size: Flow<PromptSize> =
         combine(
@@ -141,6 +158,12 @@
             }
             .distinctUntilChanged()
 
+    /** If the icon can be used as a confirmation button. */
+    val isIconConfirmButton: Flow<Boolean> =
+        combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
+            size.isNotSmall && isConfirmationRequired
+        }
+
     /** If the negative button should be shown. */
     val isNegativeButtonVisible: Flow<Boolean> =
         combine(
@@ -289,8 +312,10 @@
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
         _legacyState.value =
-            if (alreadyAuthenticated) {
+            if (alreadyAuthenticated && isConfirmationRequired.first()) {
                 AuthBiometricView.STATE_PENDING_CONFIRMATION
+            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
+                AuthBiometricView.STATE_AUTHENTICATED
             } else {
                 AuthBiometricView.STATE_HELP
             }
@@ -388,18 +413,10 @@
     }
 
     private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
-        val availableModalities = modalities.first()
         val confirmationRequired = isConfirmationRequired.first()
 
-        if (availableModalities.hasFaceAndFingerprint) {
-            // coex only needs confirmation when face is successful, unless it happens on the
-            // first attempt (i.e. without failure) before fingerprint scanning starts
-            val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
-            if (modality == BiometricModality.Face) {
-                return fingerprintStarted || confirmationRequired
-            }
-        }
-        if (availableModalities.hasFaceOnly) {
+        // Only worry about confirmationRequired if face was used to unlock
+        if (modality == BiometricModality.Face) {
             return confirmationRequired
         }
         // fingerprint only never requires confirmation
@@ -430,6 +447,26 @@
     }
 
     /**
+     * Touch event occurred on the overlay
+     *
+     * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
+     * confirmation
+     */
+    fun onOverlayTouch(event: MotionEvent): Boolean {
+        if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+            _isOverlayTouched.value = true
+
+            if (_isAuthenticated.value.needsUserConfirmation) {
+                confirmAuthenticated()
+            }
+            return true
+        } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+            _isOverlayTouched.value = false
+        }
+        return false
+    }
+
+    /**
      * Switch to the credential view.
      *
      * TODO(b/251476085): this should be decoupled from the shared panel controller
@@ -438,11 +475,26 @@
         _forceLargeSize.value = true
     }
 
-    private fun VibratorHelper.success(modality: BiometricModality) =
-        vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+    private fun VibratorHelper.success(modality: BiometricModality) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+        } else {
+            vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+        }
+    }
 
-    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
-        vibrateAuthError("$TAG, modality = $modality BP::error")
+    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+        } else {
+            vibrateAuthError("$TAG, modality = $modality BP::error")
+        }
+    }
+
+    /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+    fun clearHaptics() {
+        _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+    }
 
     companion object {
         private const val TAG = "PromptViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5ca36ab..ddb09749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -156,9 +156,9 @@
         }
         windowLayoutParams.packageName = context.opPackageName
         rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
-            override fun onViewDetachedFromWindow(view: View?) {}
+            override fun onViewDetachedFromWindow(view: View) {}
 
-            override fun onViewAttachedToWindow(view: View?) {
+            override fun onViewAttachedToWindow(view: View) {
                 layoutRipple()
                 rippleView.startRipple(Runnable {
                     windowManager.removeView(rippleView)
@@ -176,7 +176,7 @@
         val height = bounds.height()
         val maxDiameter = Integer.max(width, height) * 2f
         rippleView.setMaxSize(maxDiameter, maxDiameter)
-        when (context.display.rotation) {
+        when (context.display?.rotation) {
             Surface.ROTATION_0 -> {
                 rippleView.setCenter(
                         width * normalizedPortPosX, height * normalizedPortPosY)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
index 63d57cc..a9f3b77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -29,7 +29,7 @@
  */
 class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
     View.AccessibilityDelegate() {
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         if (action == ACTION_CLICK) {
             falsingCollector.onA11yAction()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
index 0b8e83e..1b45ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
@@ -59,7 +59,7 @@
         context.startActivity(intent, transition.first.toBundle())
         val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
         try {
-            WindowManagerGlobal.getWindowManagerService()
+            checkNotNull(WindowManagerGlobal.getWindowManagerService())
                 .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
         } catch (e: Exception) {
             Log.e(TAG, "Error overriding clipboard app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 3e6ac86..c0d1951 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -100,7 +100,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
 
     override fun getResolutionScale(): Float {
-        context.display.getDisplayInfo(displayInfo.value)
+        context.display?.getDisplayInfo(displayInfo.value)
         val maxDisplayMode =
             displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
         maxDisplayMode?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dc..323070a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
 import android.util.AttributeSet
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
 import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@
         interactionHandler.isLongPressHandlingEnabled = isEnabled
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+    }
+
     @SuppressLint("ClickableViewAccessibility")
     override fun onTouchEvent(event: MotionEvent?): Boolean {
         return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index 6b1c85f..e627b68 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -66,9 +66,9 @@
 
         contrastButtons =
             mapOf(
-                CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+                CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard),
+                CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium),
+                CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high)
             )
 
         contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 7db5968..638da86 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -190,7 +190,7 @@
                     PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
                 val servicePackageSet = serviceInfoSet.map { it.packageName }
                 prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
-                    completedSeedingPackageSet.intersect(servicePackageSet)).apply()
+                    completedSeedingPackageSet?.intersect(servicePackageSet) ?: emptySet()).apply()
 
                 var changed = false
                 favoriteComponentSet.subtract(serviceInfoSet).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 23721c9..8bae667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -193,7 +193,7 @@
 
                         ControlsAnimations.enterAnimation(pageIndicator).apply {
                             addListener(object : AnimatorListenerAdapter() {
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     // Position the tooltip if necessary after animations are complete
                                     // so we can get the position on screen. The tooltip is not
                                     // rooted in the layout root.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index ff55b76d..a13f717 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -106,10 +106,8 @@
                 }
             )
 
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-                setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
-            }
+            window?.setType(WINDOW_TYPE)
+            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
             setOnShowListener(DialogInterface.OnShowListener { _ ->
                 val editText = requireViewById<EditText>(R.id.controls_pin_input)
                 editText.setHint(instructions)
@@ -153,9 +151,7 @@
             )
         }
         return builder.create().apply {
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-            }
+            window?.setType(WINDOW_TYPE)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index c04bc87..abe3423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -384,7 +384,7 @@
                 )
             }
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     stateAnimator = null
                 }
             })
@@ -438,7 +438,7 @@
                 duration = 200L
                 interpolator = Interpolators.LINEAR
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         statusRowUpdater.invoke()
                     }
                 })
@@ -450,7 +450,7 @@
             statusAnimator = AnimatorSet().apply {
                 playSequentially(fadeOut, fadeIn)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         status.alpha = STATUS_ALPHA_ENABLED
                         statusAnimator = null
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index be50a14..98f17f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -132,8 +132,8 @@
 
     init {
         // To pass touches to the task inside TaskView.
-        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
+        window?.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
 
         setContentView(R.layout.controls_detail_dialog)
 
@@ -182,7 +182,7 @@
         }
 
         // consume all insets to achieve slide under effect
-        window.getDecorView().setOnApplyWindowInsetsListener {
+        checkNotNull(window).decorView.setOnApplyWindowInsetsListener {
             v: View, insets: WindowInsets ->
                 val l = v.getPaddingLeft()
                 val r = v.getPaddingRight()
@@ -202,7 +202,7 @@
     }
 
     fun getTaskViewBounds(): Rect {
-        val wm = context.getSystemService(WindowManager::class.java)
+        val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
         val windowMetrics = wm.getCurrentWindowMetrics()
         val rect = windowMetrics.bounds
         val metricInsets = windowMetrics.windowInsets
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index ad2b785..dbbda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -67,7 +67,8 @@
                     iconMap.put(resourceId, icon)
                 }
             }
-            return RenderInfo(icon!!.constantState.newDrawable(context.resources), fg, bg)
+            return RenderInfo(
+                checkNotNull(icon?.constantState).newDrawable(context.resources), fg, bg)
         }
 
         fun registerComponentIcon(componentName: ComponentName, icon: Drawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 84cda5a..3c2bfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -94,10 +94,8 @@
             )
         }
         cvh.visibleDialog = builder.create().apply {
-            getWindow().apply {
-                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-                show()
-            }
+            window?.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            show()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..b2c95a6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -244,7 +244,7 @@
                     cvh.clipLayer.level = it.animatedValue as Int
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         rangeAnimator = null
                     }
                 })
@@ -335,7 +335,7 @@
         }
 
         override fun onScroll(
-            e1: MotionEvent,
+            e1: MotionEvent?,
             e2: MotionEvent,
             xDiff: Float,
             yDiff: Float
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104..ac0d3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import java.util.concurrent.Executor
@@ -47,6 +48,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Main private val mainExecutor: Executor,
     private val logger: ScreenDecorationsLogger,
+    private val featureFlags: FeatureFlags,
 ) : DecorProviderFactory() {
     private val display = context.display
     private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@
                                         keyguardUpdateMonitor,
                                         mainExecutor,
                                         logger,
+                                        featureFlags,
                                 )
                         )
                     }
@@ -110,6 +113,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val mainExecutor: Executor,
     private val logger: ScreenDecorationsLogger,
+    private val featureFlags: FeatureFlags,
 ) : BoundDecorProvider() {
     override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
 
@@ -144,6 +148,7 @@
                 mainExecutor,
                 logger,
                 authController,
+                featureFlags
         )
         view.id = viewId
         view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
index bcfeeb9e..cef45dc 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -51,7 +51,7 @@
                 val callback =
                     object : ConfigurationController.ConfigurationListener {
                         override fun onConfigChanged(newConfig: Configuration?) {
-                            context.display.getMetrics(displayMetricsHolder)
+                            context.display?.getMetrics(displayMetricsHolder)
                             trySend(displayMetricsHolder)
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 7c816ce..34a80e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,9 +26,9 @@
 import android.text.format.Formatter;
 import android.util.Log;
 
+import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -52,15 +52,21 @@
     private final boolean mCanAnimateTransition;
     private final DozeParameters mDozeParameters;
     private final DozeLog mDozeLog;
-    private final StatusBarStateController mStatusBarStateController;
 
     private long mLastTimeTickElapsed = 0;
+    // If time tick is scheduled and there's not a pending runnable to cancel:
+    private boolean mTimeTickScheduled;
+    private final Runnable mCancelTimeTickerRunnable =  new Runnable() {
+        @Override
+        public void run() {
+            mTimeTicker.cancel();
+        }
+    };
 
     @Inject
     public DozeUi(Context context, AlarmManager alarmManager,
             WakeLock wakeLock, DozeHost host, @Main Handler handler,
             DozeParameters params,
-            StatusBarStateController statusBarStateController,
             DozeLog dozeLog) {
         mContext = context;
         mWakeLock = wakeLock;
@@ -70,7 +76,6 @@
         mDozeParameters = params;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
         mDozeLog = dozeLog;
-        mStatusBarStateController = statusBarStateController;
     }
 
     @Override
@@ -157,13 +162,15 @@
     }
 
     private void scheduleTimeTick() {
-        if (mTimeTicker.isScheduled()) {
+        if (mTimeTickScheduled) {
             return;
         }
+        mTimeTickScheduled = true;
+        DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
 
         long time = System.currentTimeMillis();
         long delta = roundToNextMinute(time) - System.currentTimeMillis();
-        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
         if (scheduled) {
             mDozeLog.traceTimeTickScheduled(time, time + delta);
         }
@@ -171,11 +178,11 @@
     }
 
     private void unscheduleTimeTick() {
-        if (!mTimeTicker.isScheduled()) {
+        if (!mTimeTickScheduled) {
             return;
         }
-        verifyLastTimeTick();
-        mTimeTicker.cancel();
+        mTimeTickScheduled = false;
+        DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
     }
 
     private void verifyLastTimeTick() {
@@ -205,6 +212,7 @@
         // Keep wakelock until a frame has been pushed.
         mHandler.post(mWakeLock.wrap(() -> {}));
 
+        mTimeTickScheduled = false;
         scheduleTimeTick();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index ae40f7e8..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -432,9 +432,11 @@
         }
 
         private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+            Trace.beginSection(entry.name)
             preamble(entry)
             val dumpTime = measureTimeMillis(block)
             footer(entry, dumpTime)
+            Trace.endSection()
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dump
 
+import android.os.Trace
 import java.io.PrintWriter
 
 /**
@@ -83,31 +84,33 @@
 ) {
 
     fun printTableData(pw: PrintWriter) {
+        Trace.beginSection("DumpsysTableLogger#printTableData")
         printSectionStart(pw)
         printSchema(pw)
         printData(pw)
         printSectionEnd(pw)
+        Trace.endSection()
     }
 
     private fun printSectionStart(pw: PrintWriter) {
-        pw.println(HEADER_PREFIX + sectionName)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(sectionName)
+        pw.append("version ").println(VERSION)
     }
 
     private fun printSectionEnd(pw: PrintWriter) {
-        pw.println(FOOTER_PREFIX + sectionName)
+        pw.append(FOOTER_PREFIX).println(sectionName)
     }
 
     private fun printSchema(pw: PrintWriter) {
-        pw.println(columns.joinToString(separator = SEPARATOR))
+        columns.joinTo(pw, separator = SEPARATOR).println()
     }
 
     private fun printData(pw: PrintWriter) {
         val count = columns.size
-        rows
-            .filter { it.size == count }
-            .forEach { dataLine ->
-                pw.println(dataLine.joinToString(separator = SEPARATOR))
+        rows.forEach { dataLine ->
+            if (dataLine.size == count) {
+                dataLine.joinTo(pw, separator = SEPARATOR).println()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0ed0e8..4a22a67 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -226,6 +226,12 @@
     val WALLPAPER_PICKER_PAGE_TRANSITIONS =
         unreleasedFlag("wallpaper_picker_page_transitions")
 
+    /** Add "Apply" button to wall paper picker's grid preview page. */
+    // TODO(b/294866904): Tracking bug.
+    @JvmField
+    val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
+            unreleasedFlag("wallpaper_picker_grid_apply_button")
+
     /** Whether to run the new udfps keyguard refactor code. */
     // TODO(b/279440316): Tracking bug.
     @JvmField
@@ -252,8 +258,7 @@
 
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
-    @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag("fp_listen_occluding_apps",
-            teamfood = true)
+    @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
@@ -286,11 +291,30 @@
                 teamfood = true
             )
 
+    /**
+     * TODO(b/278086361): Tracking bug
+     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+     *
+     * This flag is under development; some types of unlock may not animate properly if you enable
+     * it.
+     */
+    @JvmField
+    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+            unreleasedFlag("keyguard_wm_state_refactor")
+
     /** Stop running face auth when the display state changes to OFF. */
     // TODO(b/294221702): Tracking bug.
     @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
             R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
 
+    /** Flag to disable the face scanning animation pulsing. */
+    // TODO(b/295245791): Tracking bug.
+    @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
+            R.bool.flag_stop_pulsing_face_scanning_animation,
+            "stop_pulsing_face_scanning_animation")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -367,13 +391,13 @@
 
     // TODO(b/293863612): Tracking Bug
     @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
-        unreleasedFlag("incompatible_charging_battery_icon")
+        releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
     val INSTANT_TETHER = unreleasedFlag("instant_tether")
 
     // TODO(b/294588085): Tracking Bug
-    val WIFI_SECONDARY_NETWORKS = unreleasedFlag("wifi_secondary_networks")
+    val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -527,6 +551,12 @@
     val ENABLE_PIP_APP_ICON_OVERLAY =
         sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
 
+
+    // TODO(b/293252410) : Tracking Bug
+    @JvmField
+    val LOCKSCREEN_ENABLE_LANDSCAPE =
+            unreleasedFlag("lockscreen.enable_landscape")
+
     // TODO(b/273443374): Tracking Bug
     @Keep
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 7078341..b5b56b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -161,7 +161,7 @@
     }
 
     private fun updateIconTile() {
-        val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+        val iconTile = rootView.requireViewById(BACKLIGHT_ICON_ID) as ImageView
         val backgroundDrawable = iconTile.background as ShapeDrawable
         if (currentLevel == 0) {
             iconTile.setColorFilter(dimmedIconColor)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 public class KeyguardService extends Service {
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
+    private final FeatureFlags mFlags;
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
     private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
                            KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
                            ScreenOnCoordinator screenOnCoordinator,
                            ShellTransitions shellTransitions,
-                           DisplayTracker displayTracker) {
+                           DisplayTracker displayTracker,
+                           WindowManagerLockscreenVisibilityViewModel
+                                   wmLockscreenVisibilityViewModel,
+                           WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+                           KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+                           KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+                           @Application CoroutineScope scope,
+                           FeatureFlags featureFlags) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
         mScreenOnCoordinator = screenOnCoordinator;
         mShellTransitions = shellTransitions;
         mDisplayTracker = displayTracker;
+        mFlags = featureFlags;
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            WindowManagerLockscreenVisibilityViewBinder.bind(
+                    wmLockscreenVisibilityViewModel,
+                    wmLockscreenVisibilityManager,
+                    scope);
+
+            KeyguardSurfaceBehindViewBinder.bind(
+                    keyguardSurfaceBehindViewModel,
+                    keyguardSurfaceBehindAnimator,
+                    scope);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9a09df4..ff74050 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -403,7 +403,9 @@
      * the device.
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
-        return isNexusLauncherUnderneath() &&
+        // TODO(b/278086361): Refactor in-window animations.
+        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+                isNexusLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
                 !notificationShadeWindowController.isLaunchingActivity &&
@@ -847,54 +849,57 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                val surfaceHeight: Int =
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
-            var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                    (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                    MathUtils.clamp(amount, 0f, 1f))
+                var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+                        MathUtils.clamp(amount, 0f, 1f))
 
-            // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
-            // so don't also scale the window.
-            if (keyguardStateController.isDismissingFromSwipe &&
-                    willUnlockWithInWindowLauncherAnimations) {
-                scaleFactor = 1f
-            }
-
-            // Translate up from the bottom.
-            surfaceBehindMatrix.setTranslate(
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
-                            surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
-            )
-
-            // Scale up from a point at the center-bottom of the surface.
-            surfaceBehindMatrix.postScale(
-                    scaleFactor,
-                    scaleFactor,
-                    keyguardViewController.viewRootImpl.width / 2f,
-                    surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
-            )
-
-            // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
-            // unable to draw
-            val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
-            if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                    sc?.isValid == true) {
-                with(SurfaceControl.Transaction()) {
-                    setMatrix(sc, surfaceBehindMatrix, tmpFloat)
-                    setCornerRadius(sc, roundedCornerRadius)
-                    setAlpha(sc, animationAlpha)
-                    apply()
+                // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+                // animations, so don't also scale the window.
+                if (keyguardStateController.isDismissingFromSwipe &&
+                        willUnlockWithInWindowLauncherAnimations) {
+                    scaleFactor = 1f
                 }
-            } else {
-                applyParamsToSurface(
-                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                            surfaceBehindRemoteAnimationTarget.leash)
-                            .withMatrix(surfaceBehindMatrix)
-                            .withCornerRadius(roundedCornerRadius)
-                            .withAlpha(animationAlpha)
-                            .build()
+
+                // Translate up from the bottom.
+                surfaceBehindMatrix.setTranslate(
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+                                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
                 )
+
+                // Scale up from a point at the center-bottom of the surface.
+                surfaceBehindMatrix.postScale(
+                        scaleFactor,
+                        scaleFactor,
+                        keyguardViewController.viewRootImpl.width / 2f,
+                        surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+                )
+
+                // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+                // unable to draw
+                val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+                if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc?.isValid == true) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+                        setCornerRadius(sc, roundedCornerRadius)
+                        setAlpha(sc, animationAlpha)
+                        apply()
+                    }
+                } else {
+                    applyParamsToSurface(
+                            SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                                    surfaceBehindRemoteAnimationTarget.leash)
+                                    .withMatrix(surfaceBehindMatrix)
+                                    .withCornerRadius(roundedCornerRadius)
+                                    .withAlpha(animationAlpha)
+                                    .build()
+                    )
+                }
             }
         }
 
@@ -983,10 +988,12 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            keyguardViewController.hide(
-                surfaceBehindRemoteAnimationStartTime,
-                0 /* fadeOutDuration */
-            )
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                keyguardViewController.hide(
+                        surfaceBehindRemoteAnimationStartTime,
+                        0 /* fadeOutDuration */
+                )
+            }
         } else {
             Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
                     "showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 2b6f77d..8e323d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -137,6 +137,12 @@
     fun bindIndicationArea() {
         indicationAreaHandle?.dispose()
 
+        if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+
         indicationAreaHandle =
             KeyguardIndicationAreaBinder.bind(
                 notificationShadeWindowView,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f861d5e..fd15853 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1035,12 +1034,19 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+                        transit, apps, wallpapers, nonApps, finishedCallback);
+            }
             Trace.endSection();
         }
 
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+            }
         }
     };
 
@@ -1106,7 +1112,7 @@
 
                         mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
                         mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
-                        mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+                        //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
                         mOccludeByDreamAnimator.addUpdateListener(
                                 animation -> {
                                     SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1336,6 +1342,8 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
+    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
@@ -1379,7 +1387,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1446,8 +1455,9 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mMainDispatcher = mainDispatcher;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+        mMainDispatcher = mainDispatcher;
     }
 
     public void userActivity() {
@@ -2685,6 +2695,12 @@
             if (DEBUG) {
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager if flag is enabled.
+                return;
+            }
+
             try {
                 mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
@@ -2724,7 +2740,11 @@
             }
             mHiding = false;
 
-            mKeyguardViewControllerLazy.get().show(options);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled directly in StatusBarKeyguardViewManager if enabled.
+                mKeyguardViewControllerLazy.get().show(options);
+            }
+
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
             adjustStatusBarLocked();
@@ -2795,19 +2815,22 @@
             mUpdateMonitor.setKeyguardGoingAway(true);
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
-            // Don't actually hide the Keyguard at the moment, wait for window
-            // manager until it tells us it's safe to do so with
-            // startKeyguardExitAnimation.
-            // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
-            // order.
-            final int keyguardFlag = flags;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error while calling WindowManager", e);
-                }
-            });
+            // Handled in WmLockscreenVisibilityManager if flag is enabled.
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Don't actually hide the Keyguard at the moment, wait for window manager 
+                    // until it tells us it's safe to do so with startKeyguardExitAnimation.
+		    // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager 
+		    // will be in order.
+		    final int keyguardFlag = flags;
+		    mUiBgExecutor.execute(() -> {
+		        try {
+		            mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
+		        } catch (RemoteException e) {
+		            Log.e(TAG, "Error while calling WindowManager", e);
+		        }
+		    });
+            }
+
             Trace.endSection();
         }
     };
@@ -2919,7 +2942,10 @@
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
-                if (finishedCallback != null) {
+                // If the flag is enabled, remote animation state is handled in
+                // WmLockscreenVisibilityManager.
+                if (finishedCallback != null
+                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -2945,10 +2971,12 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                try {
-                                    finishedCallback.onAnimationFinished();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    try {
+                                        finishedCallback.onAnimationFinished();
+                                    } catch (RemoteException e) {
+                                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                    }
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2975,7 +3003,11 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+                    // short circuit when this is null.
+                    mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                }
                 mSurfaceBehindRemoteAnimationRunning = true;
 
                 mInteractionJankMonitor.begin(
@@ -2995,7 +3027,10 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled directly in StatusBarKeyguardViewManager if enabled.
+                    mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                }
 
                 // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
                 // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3009,13 +3044,17 @@
                     }
                     if (apps == null || apps.length == 0) {
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
                         try {
-                            finishedCallback.onAnimationFinished();
+                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                finishedCallback.onAnimationFinished();
+                            }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "RemoteException");
                         } finally {
                             mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         }
+
                         return;
                     }
 
@@ -3039,7 +3078,9 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3050,7 +3091,9 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3199,7 +3242,11 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            mActivityTaskManagerService.keyguardGoingAway(flags);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager.
+                mActivityTaskManagerService.keyguardGoingAway(flags);
+            }
+
             mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val activityTaskManagerService: IActivityTaskManager,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+    /**
+     * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+     * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+     */
+    private var isLockscreenShowing = true
+
+    /**
+     * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+     * to show AOD when the lockscreen is visible.
+     */
+    private var isAodVisible = false
+
+    /**
+     * Whether the keyguard is currently "going away", which we triggered via a call to
+     * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+     * the app/launcher surface behind the keyguard is made visible, and WM calls
+     * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+     * it.
+     *
+     * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+     * do that ourselves once we are done animating the surface.
+     *
+     * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+     * and we have gotten into trouble using it to mean various different things in the past. Unlock
+     * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+     * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+     * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+     * is still 'going away' because unlock animations are playing, it's actually not.
+     *
+     * If you want to know if the keyguard is 'going away', you probably want to check if we have
+     * STARTED but not FINISHED a transition to GONE.
+     *
+     * The going away animation will run until:
+     * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+     * - We call [setLockscreenShown] = true, which cancels the going away animation.
+     * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+     *   second timeout).
+     */
+    private var isKeyguardGoingAway = false
+        private set(value) {
+            // TODO(b/278086361): Extricate the keyguard state controller.
+            keyguardStateController.notifyKeyguardGoingAway(value)
+            field = value
+        }
+
+    /** Callback provided by WM to call once we're done with the going away animation. */
+    private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+     * Manager to effect the change.
+     */
+    fun setSurfaceBehindVisibility(visible: Boolean) {
+        if (isKeyguardGoingAway == visible) {
+            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+            return
+        }
+
+        // The surface behind is always visible if the lockscreen is not showing, so we're already
+        // visible.
+        if (visible && !isLockscreenShowing) {
+            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            return
+        }
+
+        if (visible) {
+            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // lockscreen is still showing as well, allowing us to animate unlocked.
+            Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+            activityTaskManagerService.keyguardGoingAway(0)
+            isKeyguardGoingAway = true
+        } else {
+            // Hide the surface by setting the lockscreen showing.
+            setLockscreenShown(true)
+        }
+    }
+
+    fun setAodVisible(aodVisible: Boolean) {
+        setWmLockscreenState(aodVisible = aodVisible)
+    }
+
+    /** Sets the visibility of the lockscreen. */
+    fun setLockscreenShown(lockscreenShown: Boolean) {
+        setWmLockscreenState(lockscreenShowing = lockscreenShown)
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationStart(
+        @WindowManager.TransitionOldType transit: Int,
+        apps: Array<RemoteAnimationTarget>,
+        wallpapers: Array<RemoteAnimationTarget>,
+        nonApps: Array<RemoteAnimationTarget>,
+        finishedCallback: IRemoteAnimationFinishedCallback
+    ) {
+        goingAwayRemoteAnimationFinishedCallback = finishedCallback
+        keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+        // If WM cancelled the animation, we need to end immediately even if we're still using the
+        // animation.
+        endKeyguardGoingAwayAnimation()
+    }
+
+    /**
+     * Whether the going away remote animation target is in-use, which means we're animating it or
+     * intend to animate it.
+     *
+     * Some unlock animations (such as the translation spring animation) are non-deterministic and
+     * might end after the transition to GONE ends. In that case, we want to keep the remote
+     * animation running until the spring ends.
+     */
+    fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+        if (!usingTarget) {
+            endKeyguardGoingAwayAnimation()
+        }
+    }
+
+    private fun setWmLockscreenState(
+        lockscreenShowing: Boolean = this.isLockscreenShowing,
+        aodVisible: Boolean = this.isAodVisible
+    ) {
+        Log.d(
+            TAG,
+            "#setWmLockscreenState(" +
+                "isLockscreenShowing=$lockscreenShowing, " +
+                "aodVisible=$aodVisible)."
+        )
+
+        if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            return
+        }
+
+        activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+        this.isLockscreenShowing = lockscreenShowing
+        this.isAodVisible = aodVisible
+    }
+
+    private fun endKeyguardGoingAwayAnimation() {
+        if (!isKeyguardGoingAway) {
+            Log.d(
+                TAG,
+                "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+                    "Short-circuiting."
+            )
+            return
+        }
+
+        executor.execute {
+            Log.d(TAG, "Finishing remote animation.")
+            goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+            goingAwayRemoteAnimationFinishedCallback = null
+
+            isKeyguardGoingAway = false
+
+            keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+        }
+    }
+
+    companion object {
+        private val TAG = this::class.java.simpleName
+    }
+}
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 a5ac7c7..9a44230 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -74,12 +75,11 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import java.util.concurrent.Executor;
+
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -146,7 +146,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -189,7 +190,8 @@
                 systemClock,
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
-                systemPropertiesHelper);
+                systemPropertiesHelper,
+                wmLockscreenVisibilityManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index c019d21..5d7a3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -59,7 +59,7 @@
         conflatedCallbackFlow {
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         val hasCards = response?.walletCards?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
@@ -71,7 +71,7 @@
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
                         trySendWithFailureLogging(
                             KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
@@ -133,13 +133,13 @@
         return suspendCancellableCoroutine { continuation ->
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         continuation.resumeWith(
                             Result.success(response?.walletCards ?: emptyList())
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         continuation.resumeWith(Result.success(emptyList()))
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 30f8f3e..6894147 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlags,
     facePropertyRepository: FacePropertyRepository,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
         // starts going to sleep.
         merge(
                 keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
-                keyguardRepository.isKeyguardGoingAway,
-                userRepository.userSwitchingInProgress
+                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+                } else {
+                    keyguardRepository.isKeyguardGoingAway
+                },
+                userRepository.userSwitchingInProgress,
             )
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
@@ -581,7 +586,7 @@
             // We always want to invoke face detect in the main thread.
             faceAuthLogger.faceDetectionStarted()
             faceManager?.detectFace(
-                detectCancellationSignal,
+                checkNotNull(detectCancellationSignal),
                 detectionCallback,
                 FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
             )
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 e35c369..42cd3a5 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
@@ -99,7 +99,16 @@
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
 
-    /** Observable for the signal that keyguard is about to go away. */
+    /**
+     * Observable for the signal that keyguard is about to go away.
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+            "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+            "whenever possible."
+    )
     val isKeyguardGoingAway: Flow<Boolean>
 
     /** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@
 
                 awaitClose { keyguardStateController.removeCallback(callback) }
             }
+            .distinctUntilChanged()
             .stateIn(
-                scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = keyguardStateController.isUnlocked,
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = false,
             )
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
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 246ee33..2f80106 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
@@ -32,6 +32,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    fun keyguardSurfaceBehindRepository(
+        impl: KeyguardSurfaceBehindRepositoryImpl
+    ): KeyguardSurfaceBehindRepository
+
+    @Binds
     fun keyguardTransitionRepository(
         impl: KeyguardTransitionRepositoryImpl
     ): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+    /** Whether we're running animations on the surface. */
+    val isAnimatingSurface: Flow<Boolean>
+
+    /** Set whether we're running animations on the surface. */
+    fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 6a2511f..1c0b73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -163,12 +163,13 @@
 
     private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
         return with(point) {
+            val display = checkNotNull(context.display)
             CircleReveal(
                 x,
                 y,
                 startRadius = 0,
                 endRadius =
-                    max(max(x, context.display.width - x), max(y, context.display.height - y)),
+                    max(max(x, display.width - x), max(y, display.height - y)),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,6 +52,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
 ) :
     TransitionInteractor(
@@ -53,6 +61,7 @@
 
     override fun start() {
         listenForLockscreenToGone()
+        listenForLockscreenToGoneDragging()
         listenForLockscreenToOccluded()
         listenForLockscreenToCamera()
         listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
         listenForLockscreenToAlternateBouncer()
     }
 
+    /**
+     * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+     * or null if we don't care and should just use a reasonable default.
+     *
+     * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+     * LOCKSCREEN is running.
+     */
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .map { startedStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // LOCKSCREEN to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@map null
+                }
+
+                true // TODO(b/278086361): Implement continuous swipe to unlock.
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+     * care and should use a reasonable default.
+     */
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+            ) { startedStep, fromLockscreenStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+                    // animation).
+                    return@combine null
+                } else if (fromLockscreenStep.value > 0.5f) {
+                    // Start the animation once we're 50% transitioned to GONE.
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
         scope.launch {
@@ -169,7 +235,8 @@
                             }
 
                             // If canceled, just put the state back
-                            // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+                            // TODO(b/278086361): This logic should happen in
+                            //  FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
                                 transitionRepository.startTransition(
                                     TransitionInfo(
@@ -201,7 +268,32 @@
         }
     }
 
+    fun dismissKeyguard() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForLockscreenToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGoneDragging() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
     }
 
     companion object {
-        private val DEFAULT_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -44,6 +49,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
 ) :
     TransitionInteractor(
@@ -57,6 +63,57 @@
         listenForPrimaryBouncerToDreamingLockscreenHosted()
     }
 
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    return@combine null
+                }
+
+                fromBouncerStep.value > 0.5f
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // BOUNCER to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@combine null
+                }
+
+                if (fromBouncerStep.value > 0.5f) {
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f,
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    fun dismissPrimaryBouncer() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(
-                    combine(
-                        keyguardInteractor.isActiveDreamLockscreenHosted,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect {
-                    (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
-                    if (
-                        !isBouncerShowing &&
-                            isActiveDreamLockscreenHosted &&
-                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
-                    ) {
-                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                    .sample(
+                            combine(
+                                    keyguardInteractor.isActiveDreamLockscreenHosted,
+                                    transitionInteractor.startedKeyguardTransitionStep,
+                                    ::Pair
+                            ),
+                            ::toTriple
+                    )
+                    .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                        if (
+                                !isBouncerShowing &&
+                                isActiveDreamLockscreenHosted &&
+                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                        ) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        }
                     }
-                }
         }
     }
 
     private fun listenForPrimaryBouncerToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            // This is handled in KeyguardSecurityContainerController and
+            // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+            // transition vs. listening to legacy state flags.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
-                            if (securityMode == Password) {
+                            if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
                                 TO_GONE_SHORT_DURATION
                             } else {
                                 TO_GONE_DURATION
@@ -188,7 +251,7 @@
 
     companion object {
         private val DEFAULT_DURATION = 300.milliseconds
-        val TO_GONE_DURATION = 250.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
         val TO_GONE_SHORT_DURATION = 200.milliseconds
     }
 }
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
index 53d3c07..562c4db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -34,12 +34,12 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
         repository.setAnimateDozingTransitions(animate)
     }
 
+    fun isKeyguardDismissable(): Boolean {
+        return repository.isKeyguardUnlocked.value
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
+
+        fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+            return when (state) {
+                KeyguardState.OFF -> true
+                KeyguardState.DOZING -> true
+                KeyguardState.DREAMING -> true
+                KeyguardState.AOD -> true
+                KeyguardState.ALTERNATE_BOUNCER -> true
+                KeyguardState.PRIMARY_BOUNCER -> true
+                KeyguardState.LOCKSCREEN -> true
+                KeyguardState.GONE -> false
+                KeyguardState.OCCLUDED -> true
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+    private val repository: KeyguardSurfaceBehindRepository,
+    private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val viewParams: Flow<KeyguardSurfaceBehindModel> =
+        transitionInteractor.isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultParams
+                } else {
+                    combine(
+                        transitionSpecificViewParams,
+                        defaultParams,
+                    ) { transitionParams, defaultParams ->
+                        transitionParams ?: defaultParams
+                    }
+                }
+            }
+
+    val isAnimatingSurface = repository.isAnimatingSurface
+
+    private val defaultParams =
+        transitionInteractor.finishedKeyguardState.map { state ->
+            KeyguardSurfaceBehindModel(
+                alpha =
+                    if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+                    else 0f
+            )
+        }
+
+    /**
+     * View params provided by the transition interactor for the most recently STARTED transition.
+     * This is used to run transition-specific animations on the surface.
+     *
+     * If null, there are no transition-specific view params needed for this transition and we will
+     * use a reasonable default.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+        transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+            when (startedStep.from) {
+                KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+                KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+                // Return null for other states, where no transition specific params are needed.
+                else -> flowOf(null)
+            }
+        }
+
+    fun setAnimatingSurface(animating: Boolean) {
+        repository.setAnimatingSurface(animating)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 8c4c7ae..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,17 +17,20 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
 class KeyguardTransitionInteractor
 @Inject
 constructor(
-    private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
+    private val repository: KeyguardTransitionRepository,
+    private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+    private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+    private val fromPrimaryBouncerTransitionInteractor:
+        dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -128,12 +136,11 @@
         repository.transition(PRIMARY_BOUNCER, GONE)
 
     /** OFF->LOCKSCREEN transition information. */
-    val offToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+    val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
 
     /** DOZING->LOCKSCREEN transition information. */
     val dozingToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+        repository.transition(DOZING, LOCKSCREEN)
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition */
+    /** The destination state of the last started transition. */
     val startedKeyguardState: StateFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     /** The last completed [KeyguardState] transition */
     val finishedKeyguardState: StateFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+    /**
+     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+     * it.
+     */
+    val isInTransitionToAnyState =
+            combine(
+                    startedKeyguardTransitionStep,
+                    finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                startedStep.to != finishedState
+            }
+
     /**
      * The amount of transition into or out of the given [KeyguardState].
      *
@@ -187,4 +207,41 @@
                 }
             }
     }
+
+    fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.from == fromState }
+    }
+
+    fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.to == toState }
+    }
+
+    /**
+     * Called to start a transition that will ultimately dismiss the keyguard from the current
+     * state.
+     */
+    fun startDismissKeyguardTransition() {
+        when (startedKeyguardState.value) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            else ->
+                Log.e(
+                    "KeyguardTransitionInteractor",
+                    "We don't know how to dismiss keyguard from state " +
+                        "${startedKeyguardState.value}"
+                )
+        }
+    }
+
+    /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+    fun isInTransitionToState(
+            state: KeyguardState,
+    ): Flow<Boolean> {
+        return combine(
+                startedKeyguardTransitionStep,
+                finishedKeyguardState,
+        ) { startedStep, finishedState ->
+            startedStep.to == state && finishedState != state
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index ff8d5c9..3ec660a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,10 +22,14 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +59,8 @@
     @Application scope: CoroutineScope,
     private val context: Context,
     activityStarter: ActivityStarter,
+    powerInteractor: PowerInteractor,
+    featureFlags: FeatureFlags,
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         combine(
@@ -87,29 +93,37 @@
             .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
 
     init {
-        scope.launch {
-            // On fingerprint success, go to the home screen
-            fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
-        }
+        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+            scope.launch {
+                // On fingerprint success when the screen is on, go to the home screen
+                fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+                    if (it) {
+                        goToHomeScreen()
+                    }
+                    // don't go to the home screen if the authentication is from AOD/dozing/off
+                }
+            }
 
-        scope.launch {
-            // On device fingerprint lockout, request the bouncer with a runnable to
-            // go to the home screen. Without this, the bouncer won't proceed to the home screen.
-            fingerprintLockoutEvents.collect {
-                activityStarter.dismissKeyguardThenExecute(
-                    object : ActivityStarter.OnDismissAction {
-                        override fun onDismiss(): Boolean {
-                            goToHomeScreen()
-                            return false
-                        }
+            scope.launch {
+                // On device fingerprint lockout, request the bouncer with a runnable to
+                // go to the home screen. Without this, the bouncer won't proceed to the home
+                // screen.
+                fingerprintLockoutEvents.collect {
+                    activityStarter.dismissKeyguardThenExecute(
+                        object : ActivityStarter.OnDismissAction {
+                            override fun onDismiss(): Boolean {
+                                goToHomeScreen()
+                                return false
+                            }
 
-                        override fun willRunAnimationOnKeyguard(): Boolean {
-                            return false
-                        }
-                    },
-                    /* cancel= */ null,
-                    /* afterKeyguardGone */ false
-                )
+                            override fun willRunAnimationOnKeyguard(): Boolean {
+                                return false
+                            }
+                        },
+                        /* cancel= */ null,
+                        /* afterKeyguardGone */ false
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+    surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+    fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+    private val defaultSurfaceBehindVisibility =
+        transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+    /**
+     * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+     * RUNNING transition, or null if the current transition does not require special surface
+     * visibility handling.
+     *
+     * An example of transition-specific visibility is swipe to unlock, where the surface should
+     * only be visible after swiping 20% of the way up the screen, and should become invisible again
+     * if the user swipes back down.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .flatMapLatest { startedStep ->
+                when (startedStep.from) {
+                    KeyguardState.LOCKSCREEN -> {
+                        fromLockscreenInteractor.surfaceBehindVisibility
+                    }
+                    KeyguardState.PRIMARY_BOUNCER -> {
+                        fromBouncerInteractor.surfaceBehindVisibility
+                    }
+                    else -> flowOf(null)
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Surface visibility, which is either determined by the default visibility in the FINISHED
+     * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val surfaceBehindVisibility: Flow<Boolean> =
+        transitionInteractor
+                .isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultSurfaceBehindVisibility
+                } else {
+                    combine(
+                        transitionSpecificSurfaceBehindVisibility,
+                        defaultSurfaceBehindVisibility,
+                    ) { transitionVisibility, defaultVisibility ->
+                        // Defer to the transition-specific visibility since we're RUNNING a
+                        // transition, but fall back to the default visibility if the current
+                        // transition's interactor did not specify a visibility.
+                        transitionVisibility ?: defaultVisibility
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+     * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+     */
+    val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+        combine(
+                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                transitionInteractor.finishedKeyguardState,
+                surfaceBehindInteractor.isAnimatingSurface
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                // We may still be animating the surface after the keyguard is fully GONE, since
+                // some animations (like the translation spring) are not tied directly to the
+                // transition step amount.
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+     *
+     * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+     * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+     * want to know if the AOD/clock/notifs/etc. are visible.
+     */
+    val lockscreenVisibility: Flow<Boolean> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                // If we finished the transition, use the finished state. If we're running a
+                // transition, use the state we're transitioning FROM. This can be different from
+                // the last finished state if a transition is interrupted. For example, if we were
+                // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+                // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+                // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+                if (finishedState == startedStep.to) finishedState else startedStep.from
+            }
+            .map(::isLockscreenVisible)
+            .distinctUntilChanged()
+
+    /**
+     * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+     * manager's perspective.
+     *
+     * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+     * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+     * clock/smartspace/notif icons are visible.
+     */
+    val aodVisibility: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isDozing,
+                keyguardInteractor.biometricUnlockState,
+            ) { isDozing, biometricUnlockState ->
+                // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+                // directly from AOD to unlocked while dozing).
+                isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+            }
+            .distinctUntilChanged()
+
+    companion object {
+        fun isSurfaceVisible(state: KeyguardState): Boolean {
+            return !isLockscreenVisible(state)
+        }
+
+        fun isLockscreenVisible(state: KeyguardState): Boolean {
+            return state != KeyguardState.GONE
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+    val alpha: Float = 1f,
+
+    /**
+     * If provided, animate from this value to [alpha] unless an animation is already running, in
+     * which case we'll animate from the current value to [alpha].
+     */
+    val animateFromAlpha: Float = alpha,
+    val translationY: Float = 0f,
+
+    /**
+     * If provided, animate from this value to [translationY] unless an animation is already
+     * running, in which case we'll animate from the current value to [translationY].
+     */
+    val animateFromTranslationY: Float = translationY,
+) {
+    fun willAnimateAlpha(): Boolean {
+        return animateFromAlpha != alpha
+    }
+
+    fun willAnimateTranslationY(): Boolean {
+        return animateFromTranslationY != translationY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
index d6883dd..5c072fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -67,14 +67,15 @@
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
                         keyguardRootViewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
-
-                            ambientIndicationArea?.alpha = alpha
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
                         }
                     }
 
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 a0a2abe..44acf4f 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
@@ -176,14 +176,15 @@
 
                     launch {
                         viewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
-
-                            ambientIndicationArea?.alpha = alpha
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
                         }
                     }
 
@@ -471,7 +472,7 @@
             return true
         }
 
-        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
     }
 
     @Deprecated("Deprecated as part of b/278057014")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index a385a0e..dc51944 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -73,25 +73,27 @@
                     launch {
                         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
                             keyguardRootViewModel.alpha.collect { alpha ->
-                                view.importantForAccessibility =
-                                    if (alpha == 0f) {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                    } else {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                    }
-
-                                indicationArea.alpha = alpha
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
                             }
                         } else {
                             viewModel.alpha.collect { alpha ->
-                                view.importantForAccessibility =
-                                    if (alpha == 0f) {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                    } else {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                    }
-
-                                indicationArea.alpha = alpha
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 63a6791..83b5463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -304,7 +304,7 @@
             return true
         }
 
-        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
 
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 162c109..82610e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -43,7 +43,7 @@
         vibratorHelper: VibratorHelper,
         activityStarter: ActivityStarter
     ): DisposableHandle {
-        val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
 
         val disposableHandle =
             view.repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..a5b00e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val keyguardViewController: KeyguardViewController,
+    private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+    private var surfaceBehind: RemoteAnimationTarget? = null
+    private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+        get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+    private val matrix = Matrix()
+    private val tmpFloat = FloatArray(9)
+
+    private var animatedTranslationY = FloatValueHolder()
+    private val translateYSpring =
+        SpringAnimation(animatedTranslationY).apply {
+            spring =
+                SpringForce().apply {
+                    stiffness = 200f
+                    dampingRatio = 1f
+                }
+            addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+            addEndListener { _, _, _, _ -> 
+                try {
+                    updateIsAnimatingSurface()
+                } catch (e: NullPointerException) {
+                    // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+                    e.printStackTrace()
+                }
+            }
+        }
+
+    private var animatedAlpha = 0f
+    private var alphaAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 500
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener {
+                animatedAlpha = it.animatedValue as Float
+                applyToSurfaceBehind()
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        updateIsAnimatingSurface()
+                    }
+                }
+            )
+        }
+
+    /**
+     * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+     * these will be applied once someone gives us a surface via [applyParamsToSurface].
+     */
+    var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+        set(newParams) {
+            field = newParams
+            startOrUpdateAnimators()
+            applyToSurfaceBehind()
+        }
+
+    /**
+     * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+     * any necessary animations.
+     */
+    fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+        this.surfaceBehind = surface
+        startOrUpdateAnimators()
+    }
+
+    /**
+     * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+     * Attempting to animate a released target will cause a crash.
+     *
+     * This can be called either because we finished animating the surface naturally, or by WM
+     * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+     * latter, cancel any outstanding animations we have.
+     */
+    fun notifySurfaceReleased() {
+        surfaceBehind = null
+
+        if (alphaAnimator.isRunning) {
+            alphaAnimator.cancel()
+        }
+
+        if (translateYSpring.isRunning) {
+            translateYSpring.cancel()
+        }
+    }
+
+    private fun startOrUpdateAnimators() {
+        if (surfaceBehind == null) {
+            return
+        }
+
+        if (viewParams.willAnimateAlpha()) {
+            var fromAlpha = viewParams.animateFromAlpha
+
+            if (alphaAnimator.isRunning) {
+                alphaAnimator.cancel()
+                fromAlpha = animatedAlpha
+            }
+
+            alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+            alphaAnimator.start()
+        }
+
+        if (viewParams.willAnimateTranslationY()) {
+            if (!translateYSpring.isRunning) {
+                // If the spring isn't running yet, set the start value. Otherwise, respect the
+                // current position.
+                animatedTranslationY.value = viewParams.animateFromTranslationY
+            }
+
+            translateYSpring.animateToFinalPosition(viewParams.translationY)
+        }
+
+        updateIsAnimatingSurface()
+    }
+
+    private fun updateIsAnimatingSurface() {
+        interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+    }
+
+    private fun applyToSurfaceBehind() {
+        surfaceBehind?.leash?.let { sc ->
+            executor.execute {
+                if (surfaceBehind == null) {
+                    Log.d(
+                        TAG,
+                        "Attempting to modify params of surface that isn't " +
+                            "animating. Ignoring."
+                    )
+                    matrix.set(Matrix.IDENTITY_MATRIX)
+                    return@execute
+                }
+
+                val translationY =
+                    if (translateYSpring.isRunning) animatedTranslationY.value
+                    else viewParams.translationY
+
+                val alpha =
+                    if (alphaAnimator.isRunning) {
+                        animatedAlpha
+                    } else {
+                        viewParams.alpha
+                    }
+
+                if (
+                    keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc.isValid
+                ) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(
+                            sc,
+                            matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+                            tmpFloat
+                        )
+                        setAlpha(sc, alpha)
+                        apply()
+                    }
+                } else {
+                    surfaceTransactionApplier.scheduleApply(
+                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+                            .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+                            .withAlpha(alpha)
+                            .build()
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: KeyguardSurfaceBehindViewModel,
+        applier: KeyguardSurfaceBehindParamsApplier,
+        scope: CoroutineScope
+    ) {
+        scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
index b568a9a..3bb01f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -42,13 +42,13 @@
         view.accessibilityDelegate = viewModel.accessibilityDelegate
 
         // bind child views
-        UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+        UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
         UdfpsFingerprintViewBinder.bind(
-            view.findViewById(R.id.udfps_lockscreen_fp),
+            view.requireViewById(R.id.udfps_lockscreen_fp),
             fingerprintViewModel
         )
         UdfpsBackgroundViewBinder.bind(
-            view.findViewById(R.id.udfps_keyguard_fp_bg),
+            view.requireViewById(R.id.udfps_keyguard_fp_bg),
             backgroundViewModel
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: WindowManagerLockscreenVisibilityViewModel,
+        lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+        scope: CoroutineScope
+    ) {
+        scope.launch {
+            viewModel.surfaceBehindVisibility.collect {
+                lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.lockscreenVisibility.collect {
+                lockscreenVisibilityManager.setLockscreenShown(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+        }
+
+        scope.launch {
+            viewModel.surfaceBehindAnimating.collect {
+                lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index cf96458..dd3da97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -117,7 +117,7 @@
     private var host: SurfaceControlViewHost
 
     val surfacePackage: SurfaceControlViewHost.SurfacePackage
-        get() = host.surfacePackage
+        get() = checkNotNull(host.surfacePackage)
 
     private lateinit var largeClockHostView: FrameLayout
     private lateinit var smallClockHostView: FrameLayout
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+    val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 11e85d0..6d3b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -51,15 +52,14 @@
             )
 
     /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey =
-        authenticationInteractor.canSwipeToDismiss
-            .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
-            )
+    val upDestinationSceneKey: Flow<SceneKey> =
+        authenticationInteractor.isUnlocked.map { isUnlocked ->
+            if (isUnlocked) {
+                SceneKey.Gone
+            } else {
+                SceneKey.Bouncer
+            }
+        }
 
     /** Notifies that the lock button on the lock screen was clicked. */
     fun onLockButtonClicked() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+    val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+    val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+    val lockscreenVisibility = interactor.lockscreenVisibility
+    val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e064839..5f7991e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,7 +70,7 @@
     var lifecycleOwner: ViewLifecycleOwner? = null
     val onAttachListener =
         object : View.OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(v: View?) {
+            override fun onViewAttachedToWindow(v: View) {
                 Assert.isMainThread()
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner =
@@ -81,7 +81,7 @@
                     )
             }
 
-            override fun onViewDetachedFromWindow(v: View?) {
+            override fun onViewDetachedFromWindow(v: View) {
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner = null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index cc1504a..b6577f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@
         return factory.create("ShadeLog", 500, false);
     }
 
+    /** Provides a logging buffer for Shade messages. */
+    @Provides
+    @SysUISingleton
+    @ShadeTouchLog
+    public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+        return factory.create("ShadeTouchLog", 500, false);
+    }
+
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 0000000..b13667e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -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 com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 67a985e..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -302,14 +302,14 @@
 
     @Synchronized
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println(HEADER_PREFIX + name)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(name)
+        pw.append("version ").println(VERSION)
 
         lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
         for (i in 0 until buffer.size) {
             buffer[i].dump(pw)
         }
-        pw.println(FOOTER_PREFIX + name)
+        pw.append(FOOTER_PREFIX).println(name)
     }
 
     /** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 35f5a8c..a91917a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -514,7 +514,7 @@
          * Returns true when the down event of the scroll hits within the target box of the thumb.
          */
         override fun onScroll(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             distanceX: Float,
             distanceY: Float
@@ -528,7 +528,7 @@
          * Gestures that include a fling are considered a false gesture on the seek bar.
          */
         override fun onFling(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             velocityX: Float,
             velocityY: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 207df6b..a1291a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -149,11 +149,7 @@
         // Check if smartspace has explicitly specified whether to re-activate resumable media.
         // The default behavior is to trigger if the smartspace data is active.
         val shouldTriggerResume =
-            if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
-                data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
-            } else {
-                true
-            }
+            data.cardAction?.extras?.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) ?: true
         val shouldReactivate =
             shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
 
@@ -269,9 +265,7 @@
                     "Cannot create dismiss action click action: extras missing dismiss_intent."
                 )
             } else if (
-                dismissIntent.getComponent() != null &&
-                    dismissIntent.getComponent().getClassName() ==
-                        EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+                dismissIntent.component?.className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
             ) {
                 // Dismiss the card Smartspace data through Smartspace trampoline activity.
                 context.startActivity(dismissIntent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 55dcd88..dddbeda 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -22,6 +22,7 @@
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
 import android.app.StatusBarManager
+import android.app.smartspace.SmartspaceAction
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
@@ -1623,20 +1624,18 @@
      *   SmartspaceTarget's data is invalid.
      */
     private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
-        var dismissIntent: Intent? = null
-        if (target.baseAction != null && target.baseAction.extras != null) {
-            dismissIntent =
-                target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
-                    as Intent?
-        }
+        val baseAction: SmartspaceAction? = target.baseAction
+        val dismissIntent =
+            baseAction?.extras?.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
 
         val isActive =
             when {
                 !mediaFlags.isPersistentSsCardEnabled() -> true
-                target.baseAction == null -> true
-                else ->
-                    target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
-                        EXTRA_VALUE_TRIGGER_PERIODIC
+                baseAction == null -> true
+                else -> {
+                    val triggerSource = baseAction.extras?.getString(EXTRA_KEY_TRIGGER_SOURCE)
+                    triggerSource != EXTRA_VALUE_TRIGGER_PERIODIC
+                }
             }
 
         packageName(target)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index d6f941d..6a8ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -65,7 +65,7 @@
 
     private val sessionListener =
         object : MediaSessionManager.OnActiveSessionsChangedListener {
-            override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            override fun onActiveSessionsChanged(controllers: List<MediaController>?) {
                 handleControllersChanged(controllers)
             }
         }
@@ -190,16 +190,18 @@
         }
     }
 
-    private fun handleControllersChanged(controllers: List<MediaController>) {
+    private fun handleControllersChanged(controllers: List<MediaController>?) {
         packageControllers.clear()
-        controllers.forEach { controller ->
+        controllers?.forEach { controller ->
             packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
                 ?: run {
                     val tokens = mutableListOf(controller)
                     packageControllers.put(controller.packageName, tokens)
                 }
         }
-        tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+        controllers?.map { TokenId(it.sessionToken) }?.let {
+            tokensWithNotifications.retainAll(it)
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..b9cc772 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -195,7 +195,7 @@
                 }
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             backgroundAnimation = null
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..646d1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -98,11 +98,11 @@
                         addListener(
                             object : AnimatorListenerAdapter() {
                                 var cancelled = false
-                                override fun onAnimationCancel(animation: Animator?) {
+                                override fun onAnimationCancel(animation: Animator) {
                                     cancelled = true
                                 }
 
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     if (cancelled) {
                                         return
                                     }
@@ -226,7 +226,7 @@
                 )
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             rippleData.progress = 0f
                             rippleAnimation = null
                             invalidateSelf()
@@ -270,11 +270,8 @@
         return bounds
     }
 
-    override fun onStateChange(stateSet: IntArray?): Boolean {
+    override fun onStateChange(stateSet: IntArray): Boolean {
         val changed = super.onStateChange(stateSet)
-        if (stateSet == null) {
-            return changed
-        }
 
         val wasPressed = pressed
         var enabled = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 1ace316..ce50a11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -127,19 +127,19 @@
         object : GestureDetector.SimpleOnGestureListener() {
             override fun onFling(
                 eStart: MotionEvent?,
-                eCurrent: MotionEvent?,
+                eCurrent: MotionEvent,
                 vX: Float,
                 vY: Float
             ) = onFling(vX, vY)
 
             override fun onScroll(
                 down: MotionEvent?,
-                lastMotion: MotionEvent?,
+                lastMotion: MotionEvent,
                 distanceX: Float,
                 distanceY: Float
-            ) = onScroll(down!!, lastMotion!!, distanceX)
+            ) = onScroll(down!!, lastMotion, distanceX)
 
-            override fun onDown(e: MotionEvent?): Boolean {
+            override fun onDown(e: MotionEvent): Boolean {
                 if (falsingProtectionNeeded) {
                     falsingCollector.onNotificationStartDismissing()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index fe8ebaf..c1c757e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -180,20 +180,20 @@
                 object : AnimatorListenerAdapter() {
                     private var cancelled: Boolean = false
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                         animationPending = false
                         rootView?.removeCallbacks(startAnimation)
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         isCrossFadeAnimatorRunning = false
                         if (!cancelled) {
                             applyTargetStateIfNotAnimating()
                         }
                     }
 
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         cancelled = false
                         animationPending = false
                     }
@@ -606,7 +606,7 @@
         val viewHost = UniqueObjectHostView(context)
         viewHost.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(p0: View?) {
+                override fun onViewAttachedToWindow(p0: View) {
                     if (rootOverlay == null) {
                         rootView = viewHost.viewRootImpl.view
                         rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
@@ -614,7 +614,7 @@
                     viewHost.removeOnAttachStateChangeListener(this)
                 }
 
-                override fun onViewDetachedFromWindow(p0: View?) {}
+                override fun onViewDetachedFromWindow(p0: View) {}
             }
         )
         return viewHost
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index be570b4..631a0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -144,12 +144,12 @@
         setListeningToMediaData(true)
         hostView.addOnAttachStateChangeListener(
             object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View?) {
+                override fun onViewAttachedToWindow(v: View) {
                     setListeningToMediaData(true)
                     updateViewVisibility()
                 }
 
-                override fun onViewDetachedFromWindow(v: View?) {
+                override fun onViewDetachedFromWindow(v: View) {
                     setListeningToMediaData(false)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..16dfc21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -117,7 +117,7 @@
                     }
                     addListener(
                         object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator?) {
+                            override fun onAnimationEnd(animation: Animator) {
                                 heightAnimator = null
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index bbd3d33..da8e106 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -201,13 +201,13 @@
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
-        val packageName = newInfo.routeInfo.clientPackageName
+        val packageName: String? = newInfo.routeInfo.clientPackageName
         var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context,
             packageName,
             isReceiver = true,
         ) {
-            logger.logPackageNotFound(packageName)
+            packageName?.let { logger.logPackageNotFound(it) }
         }
 
         if (newInfo.appNameOverride != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
index 5013802..fbf7e25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -68,9 +68,9 @@
         )
         rippleView.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewDetachedFromWindow(view: View?) {}
+                override fun onViewDetachedFromWindow(view: View) {}
 
-                override fun onViewAttachedToWindow(view: View?) {
+                override fun onViewAttachedToWindow(view: View) {
                     if (view == null) {
                         return
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 0b0535d..35018f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -54,7 +54,7 @@
         // Reset all listeners to animator.
         animator.removeAllListeners()
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 onAnimationEnd?.run()
                 isStarted = false
             }
@@ -86,7 +86,7 @@
             invalidate()
         }
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 animation?.let { visibility = GONE }
                 onAnimationEnd?.run()
                 isStarted = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index f75f8b9..87d0098 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -162,7 +162,7 @@
         logger: MediaTttSenderLogger,
         instanceId: InstanceId,
     ): ChipbarInfo {
-        val packageName = routeInfo.clientPackageName
+        val packageName = checkNotNull(routeInfo.clientPackageName)
         val otherDeviceName =
             if (routeInfo.name.isBlank()) {
                 context.getString(R.string.media_ttt_default_device_type)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index c816446..64de9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -88,7 +88,7 @@
                 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
                 as ViewGroup
 
-        val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+        val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
         container.setTaskHeightSize()
 
         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
index 38d4e69..6480a47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -81,8 +81,8 @@
             return MediaProjectionState.EntireScreen
         }
         val matchingTask =
-            tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
-                ?: return MediaProjectionState.EntireScreen
+            tasksRepository.findRunningTaskFromWindowContainerToken(
+                checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
         return MediaProjectionState.SingleTask(matchingTask)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 4d30634..63d4634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -77,8 +77,13 @@
             .build()
     }
 
-    private fun PackageManager.getApplicationLabel(packageName: String?): String? =
-        runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+    private fun PackageManager.getApplicationLabel(packageName: String?): String? {
+        if (packageName == null) {
+            return null
+        }
+
+        return runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
             .getOrNull()
             ?.let { info -> getApplicationLabel(info).toString() }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index 5f338c3..46c8d35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -132,13 +132,13 @@
             LayoutInflater.from(context)
                 .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
 
-        noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+        noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
             onGotItClicked()
         }
 
         // The Tile preview has colorBackground as its background. Change it so it's different than
         // the activity's background.
-        val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+        val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
         val shape = item.background as GradientDrawable
         val ta =
             context.theme.obtainStyledAttributes(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
index 88b8676..fedbdec 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
@@ -53,11 +53,14 @@
 
     @VisibleForTesting
     companion object {
-        val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
-                AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
+        val OPS_MIC_CAMERA = intArrayOf(
+                AppOpsManager.OP_CAMERA,
+                AppOpsManager.OP_PHONE_CALL_CAMERA,
+                AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_PHONE_CALL_MICROPHONE,
                 AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
-                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO)
+                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+                AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO)
         val OPS_LOCATION = intArrayOf(
                 AppOpsManager.OP_COARSE_LOCATION,
                 AppOpsManager.OP_FINE_LOCATION)
@@ -212,6 +215,7 @@
             AppOpsManager.OP_PHONE_CALL_MICROPHONE,
             AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
             AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
             AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
             else -> return null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f4aa27d..c202f14 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -192,7 +192,7 @@
             return null
         }
         val closeAppButton =
-            window.layoutInflater.inflate(
+            checkNotNull(window).layoutInflater.inflate(
                 R.layout.privacy_dialog_card_button,
                 expandedLayout,
                 false
@@ -248,7 +248,7 @@
 
     private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
         val manageButton =
-            window.layoutInflater.inflate(
+            checkNotNull(window).layoutInflater.inflate(
                 R.layout.privacy_dialog_card_button,
                 expandedLayout,
                 false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885..d2eac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@
 import android.graphics.Path;
 import android.graphics.PointF;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.io.PrintWriter;
@@ -129,6 +131,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
index 2d45c5b2..86ef7ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
@@ -10,6 +10,17 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "QuickSettingsDeviceResetTests",
+      "options": [
+          {
+            "exclude-annotation": "org.junit.Ignore"
+          },
+          {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
+          }
+      ]
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index a162d11..18f59b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.database.ContentObserver
 import android.provider.Settings
+import android.util.SparseArray
 import com.android.systemui.R
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -107,6 +108,7 @@
 ) : TileSpecRepository {
 
     private val mutex = Mutex()
+    private val tileSpecsPerUser = SparseArray<List<TileSpec>>()
 
     private val retailModeTiles by lazy {
         resources
@@ -142,10 +144,12 @@
                 awaitClose { secureSettings.unregisterContentObserver(observer) }
             }
             .onStart { emit(Unit) }
-            .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
-            .distinctUntilChanged()
+            .map { loadTiles(userId) }
             .onEach { logger.logTilesChangedInSettings(it, userId) }
-            .map { parseTileSpecs(it, userId) }
+            .distinctUntilChanged()
+            .map { parseTileSpecs(it, userId).also { storeTiles(userId, it) } }
+            .distinctUntilChanged()
+            .onEach { mutex.withLock { tileSpecsPerUser.put(userId, it) } }
             .flowOn(backgroundDispatcher)
     }
 
@@ -154,7 +158,7 @@
             if (tile == TileSpec.Invalid) {
                 return
             }
-            val tilesList = loadTiles(userId).toMutableList()
+            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
             if (tile !in tilesList) {
                 if (position < 0 || position >= tilesList.size) {
                     tilesList.add(tile)
@@ -162,6 +166,7 @@
                     tilesList.add(position, tile)
                 }
                 storeTiles(userId, tilesList)
+                tileSpecsPerUser.put(userId, tilesList)
             }
         }
 
@@ -170,9 +175,10 @@
             if (tiles.all { it == TileSpec.Invalid }) {
                 return
             }
-            val tilesList = loadTiles(userId).toMutableList()
+            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
             if (tilesList.removeAll(tiles)) {
                 storeTiles(userId, tilesList.toList())
+                tileSpecsPerUser.put(userId, tilesList)
             }
         }
 
@@ -181,18 +187,10 @@
             val filtered = tiles.filter { it != TileSpec.Invalid }
             if (filtered.isNotEmpty()) {
                 storeTiles(userId, filtered)
+                tileSpecsPerUser.put(userId, tiles)
             }
         }
 
-    private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
-        return withContext(backgroundDispatcher) {
-            (secureSettings.getStringForUser(SETTING, forUser) ?: "")
-                .split(DELIMITER)
-                .map(TileSpec::create)
-                .filter { it !is TileSpec.Invalid }
-        }
-    }
-
     private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
         if (retailModeRepository.inRetailMode) {
             // No storing tiles when in retail mode
@@ -214,6 +212,12 @@
         }
     }
 
+    private suspend fun loadTiles(userId: Int): String {
+        return withContext(backgroundDispatcher) {
+            secureSettings.getStringForUser(SETTING, userId) ?: ""
+        }
+    }
+
     private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> {
         val fromSettings =
             tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index ff881f7..966f370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -56,6 +56,8 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -273,7 +275,9 @@
     }
 
     override fun addTile(spec: TileSpec, position: Int) {
-        scope.launch {
+        scope.launch(backgroundDispatcher) {
+            // Block until the list is not empty
+            currentTiles.filter { it.isNotEmpty() }.first()
             tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 8f9cf4b..21da596 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -142,6 +142,9 @@
         state.contentDescription = state.label;
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.secondaryLabel = state.value
+                ? ""
+                : mContext.getString(R.string.quick_settings_work_mode_paused_state);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index cf7abdd..76d9b03 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -17,21 +17,22 @@
 package com.android.systemui.scene.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.RemoteUserInput
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Generic business logic and app state accessors for the scene framework.
@@ -44,6 +45,7 @@
 class SceneInteractor
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val repository: SceneContainerRepository,
     private val logger: SceneLogger,
 ) {
@@ -88,6 +90,22 @@
      */
     val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
 
+    /**
+     * The key of the scene that the UI is currently transitioning to or `null` if there is no
+     * active transition at the moment.
+     *
+     * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+     * like Java code.
+     */
+    val transitioningTo: StateFlow<SceneKey?> =
+        transitionState
+            .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
     /** Whether the scene container is visible. */
     val isVisible: StateFlow<Boolean> = repository.isVisible
 
@@ -142,21 +160,6 @@
         repository.setTransitionState(transitionState)
     }
 
-    /**
-     * Returns a stream of events that emits one [Unit] every time the framework transitions from
-     * [from] to [to].
-     */
-    fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> {
-        return transitionState
-            .mapNotNull { it as? ObservableTransitionState.Idle }
-            .map { idleState -> idleState.scene }
-            .distinctUntilChanged()
-            .pairwise()
-            .mapNotNull { (previousSceneKey, currentSceneKey) ->
-                Unit.takeIf { previousSceneKey == from && currentSceneKey == to }
-            }
-    }
-
     /** Handles a remote user input. */
     fun onRemoteUserInput(input: RemoteUserInput) {
         _remoteUserInput.value = input
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index afefccb..1747099 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -178,12 +178,24 @@
                         }
                         WakefulnessState.STARTING_TO_WAKE -> {
                             val authMethod = authenticationInteractor.getAuthenticationMethod()
-                            if (authMethod == AuthenticationMethodModel.None) {
-                                switchToScene(
-                                    targetSceneKey = SceneKey.Gone,
-                                    loggingReason =
-                                        "device is starting to wake up while auth method is None",
-                                )
+                            val isUnlocked = authenticationInteractor.isUnlocked.value
+                            when {
+                                authMethod == AuthenticationMethodModel.None -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while auth method is" +
+                                                " none",
+                                    )
+                                }
+                                authMethod.isSecure && isUnlocked -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while unlocked with a" +
+                                                " secure auth method",
+                                    )
+                                }
                             }
                         }
                         else -> Unit
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index b340043..23894a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -50,22 +50,20 @@
 
     public override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window.apply {
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.CENTER)
-        }
+        window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+        window?.setGravity(Gravity.CENTER)
         setContentView(R.layout.screen_share_dialog)
-        dialogTitle = findViewById(R.id.screen_share_dialog_title)
-        warning = findViewById(R.id.text_warning)
-        startButton = findViewById(android.R.id.button1)
-        cancelButton = findViewById(android.R.id.button2)
+        dialogTitle = requireViewById(R.id.screen_share_dialog_title)
+        warning = requireViewById(R.id.text_warning)
+        startButton = requireViewById(android.R.id.button1)
+        cancelButton = requireViewById(android.R.id.button2)
         updateIcon()
         initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
     }
 
     private fun updateIcon() {
-        val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+        val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
         if (dialogIconTint != null) {
             icon.setColorFilter(context.getColor(dialogIconTint))
         }
@@ -92,7 +90,7 @@
                 options
             )
         adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
-        screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+        screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 604d449..e8683fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -100,11 +100,11 @@
     @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
 
     private fun initRecordOptionsView() {
-        audioSwitch = findViewById(R.id.screenrecord_audio_switch)
-        tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
-        tapsView = findViewById(R.id.show_taps)
+        audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
+        tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
+        tapsView = requireViewById(R.id.show_taps)
         updateTapsViewVisibility()
-        options = findViewById(R.id.screen_recording_options)
+        options = requireViewById(R.id.screen_recording_options)
         val a: ArrayAdapter<*> =
             ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index ecd4568..aa6bfc3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -83,7 +83,7 @@
         if (overrideTransition) {
             val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
             try {
-                WindowManagerGlobal.getWindowManagerService()
+                checkNotNull(WindowManagerGlobal.getWindowManagerService())
                     .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
             } catch (e: Exception) {
                 Log.e(TAG, "Error overriding screenshot app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e..f4d19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@
     private float mViewAlpha = 1.0f;
     private Drawable mDrawable;
     private PorterDuffColorFilter mColorFilter;
+    private String mScrimName;
     private int mTintColor;
     private boolean mBlendWithMainColor = true;
     private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@
         }
     }
 
+    public void setScrimName(String scrimName) {
+        mScrimName = scrimName;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+    }
+
     /**
      * The position of the bottom of the scrim, used for clipping.
      * @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
          * @see NPVCDownEventState.asStringList
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc86..c501d88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     public void dispatchConfigurationChanged(Configuration newConfig) {
         super.dispatchConfigurationChanged(newConfig);
         mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 35fd98c..132cd61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1117,7 +1117,8 @@
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
                 mDreamingToLockscreenTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1153,7 +1154,8 @@
         collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition, mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
                 mLockscreenToDreamingTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -2129,6 +2131,7 @@
         }
         updateExpansionAndVisibility();
         mNotificationStackScrollLayoutController.setPanelFlinging(false);
+        mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
         // expandImmediate should be always reset at the end of animation
         mQsController.setExpandImmediate(false);
     }
@@ -2664,6 +2667,11 @@
             setListening(true);
         }
         if (mBarState != SHADE) {
+            // TODO(b/277909752): remove below logs when bug is fixed
+            mShadeLog.d("onExpandingFinished called");
+            if (mSplitShadeEnabled && !mQsController.getExpanded()) {
+                mShadeLog.d("onExpandingFinished called before QS got expanded");
+            }
             // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
             // on keyguard panel state is always OPEN so we need to have that extra update
             mQsController.setExpandImmediate(false);
@@ -3447,11 +3455,13 @@
         ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
         ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
         ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+        Trace.beginSection("Table<DownEvents>");
         new DumpsysTableLogger(
                 TAG,
                 NPVCDownEventState.TABLE_HEADERS,
                 mLastDownEvents.toList()
         ).printTableData(ipw);
+        Trace.endSection();
     }
 
     @Override
@@ -4714,6 +4724,7 @@
     }
 
     private void onPanelStateChanged(@PanelState int state) {
+        mShadeLog.logPanelStateChanged(state);
         mQsController.updateExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
@@ -4740,6 +4751,16 @@
         mCurrentPanelState = state;
     }
 
+    private Consumer<Float> setDreamLockscreenTransitionAlpha(
+            NotificationStackScrollLayoutController stackScroller) {
+        return (Float alpha) -> {
+            // Also animate the status bar's alpha during transitions between the lockscreen and
+            // dreams.
+            mKeyguardStatusBarViewController.setAlpha(alpha);
+            setTransitionAlpha(stackScroller).accept(alpha);
+        };
+    }
+
     private Consumer<Float> setTransitionAlpha(
             NotificationStackScrollLayoutController stackScroller) {
         return (Float alpha) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b328c55..6f726a2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -840,13 +840,17 @@
         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
         if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+            Trace.beginSection("mWindowRootView.dump()");
             mWindowRootView.getViewRootImpl().dump("  ", pw);
+            Trace.endSection();
         }
+        Trace.beginSection("Table<State>");
         new DumpsysTableLogger(
                 TAG,
                 NotificationShadeWindowState.TABLE_HEADERS,
                 mStateBuffer.toList()
         ).printTableData(pw);
+        Trace.endSection();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index d252943..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -170,7 +170,7 @@
          * @see [NotificationShadeWindowState.asStringList]
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index a9c4aeb..f9b4e67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -107,6 +107,8 @@
 
         result = result != null ? result : super.dispatchTouchEvent(ev);
 
+        TouchLogger.logDispatchTouch(TAG, ev, result);
+
         mInteractionEventHandler.dispatchTouchEventComplete();
 
         return result;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5b5785e..832a25b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -91,6 +91,7 @@
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final LockIconViewController mLockIconViewController;
+    private final ShadeLogger mShadeLogger;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -151,6 +152,7 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
+            ShadeLogger shadeLogger,
             PulsingGestureListener pulsingGestureListener,
             LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -176,6 +178,7 @@
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
         mBackActionInteractor = backActionInteractor;
+        mShadeLogger = shadeLogger;
         mLockIconViewController.init();
         mService = centralSurfaces;
         mPowerInteractor = powerInteractor;
@@ -223,6 +226,13 @@
         return mView.findViewById(R.id.keyguard_message_area);
     }
 
+    private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+        }
+        return result;
+    }
+
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -237,8 +247,8 @@
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                 if (mStatusBarViewController == null) { // Fix for b/192490822
-                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
-                    return false;
+                    return logDownDispatch(ev,
+                            "Ignoring touch while statusBarView not yet set", false);
                 }
                 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
                 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -250,10 +260,9 @@
                 }
 
                 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still
-                // gets
-                // delivered.
+                // gets delivered.
                 if (!isCancel && mService.shouldIgnoreTouch()) {
-                    return false;
+                    return logDownDispatch(ev, "touch ignored by CS", false);
                 }
 
                 if (isDown) {
@@ -265,8 +274,11 @@
                     mTouchActive = false;
                     mDownEvent = null;
                 }
-                if (mTouchCancelled || mExpandAnimationRunning) {
-                    return false;
+                if (mTouchCancelled) {
+                    return logDownDispatch(ev, "touch cancelled", false);
+                }
+                if (mExpandAnimationRunning) {
+                    return logDownDispatch(ev, "expand animation running", false);
                 }
 
                 if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -280,17 +292,17 @@
                 }
 
                 if (mIsOcclusionTransitionRunning) {
-                    return false;
+                    return logDownDispatch(ev, "occlusion transition running", false);
                 }
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
                 if (mDreamingWakeupGestureHandler != null
                         && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
-                    return true;
+                    return logDownDispatch(ev, "dream wakeup gesture handled", true);
                 }
                 if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
-                    return true;
+                    return logDownDispatch(ev, "dispatched to Keyguard", true);
                 }
                 if (mBrightnessMirror != null
                         && mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -298,7 +310,7 @@
                     // you can't touch anything other than the brightness slider while the mirror is
                     // showing and the rest of the panel is transparent.
                     if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                        return false;
+                        return logDownDispatch(ev, "disallowed new pointer", false);
                     }
                 }
                 if (isDown) {
@@ -329,7 +341,9 @@
                     expandingBelowNotch = true;
                 }
                 if (expandingBelowNotch) {
-                    return mStatusBarViewController.sendTouchToView(ev);
+                    return logDownDispatch(ev,
+                            "expand below notch. sending touch to status bar",
+                            mStatusBarViewController.sendTouchToView(ev));
                 }
 
                 if (!mIsTrackingBarGesture && isDown
@@ -339,9 +353,10 @@
                     if (mStatusBarViewController.touchIsWithinView(x, y)) {
                         if (mStatusBarWindowStateController.windowIsShowing()) {
                             mIsTrackingBarGesture = true;
-                            return mStatusBarViewController.sendTouchToView(ev);
-                        } else { // it's hidden or hiding, don't send to notification shade.
-                            return true;
+                            return logDownDispatch(ev, "sending touch to status bar",
+                                    mStatusBarViewController.sendTouchToView(ev));
+                        } else {
+                            return logDownDispatch(ev, "hidden or hiding", true);
                         }
                     }
                 } else if (mIsTrackingBarGesture) {
@@ -349,10 +364,10 @@
                     if (isUp || isCancel) {
                         mIsTrackingBarGesture = false;
                     }
-                    return sendToStatusBar;
+                    return logDownDispatch(ev, "sending bar gesture to status bar",
+                            sendToStatusBar);
                 }
-
-                return null;
+                return logDownDispatch(ev, "no custom touch dispatch of down event", null);
             }
 
             @Override
@@ -364,18 +379,26 @@
             public boolean shouldInterceptTouchEvent(MotionEvent ev) {
                 if (mStatusBarStateController.isDozing() && !mService.isPulsing()
                         && !mDockManager.isDocked()) {
-                    // Capture all touch events in always-on.
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: capture all touch events in always-on");
+                    }
                     return true;
                 }
 
                 if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
                     // Don't allow touches to proceed to underlying views if alternate
                     // bouncer is showing
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: alt bouncer showing");
+                    }
                     return true;
                 }
 
                 if (mLockIconViewController.onInterceptTouchEvent(ev)) {
                     // immediately return true; don't send the touch to the drag down helper
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+                    }
                     return true;
                 }
 
@@ -383,7 +406,13 @@
                         && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
-                    return mDragDownHelper.onInterceptTouchEvent(ev);
+                    boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+                    if (result) {
+                        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                            mShadeLogger.d("NSWVC: drag down helper intercepted");
+                        }
+                    }
+                    return result;
                 } else {
                     return false;
                 }
@@ -495,6 +524,7 @@
     }
 
     public void cancelCurrentTouch() {
+        mShadeLogger.d("NSWVC: cancelling current touch");
         if (mTouchActive) {
             final long now = mClock.uptimeMillis();
             final MotionEvent event;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 3b3df50..a4e439b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,6 +22,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
@@ -183,6 +184,12 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+                super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (mIsMigratingNSSL) {
             return super.drawChild(canvas, child, drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index e85024e..c9c911b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -787,6 +787,12 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
+        // TODO(b/277909752): remove below log when bug is fixed
+        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+            Log.wtf(TAG,
+                    "setting QS height to 0 in split shade while shade is open(ing). "
+                            + "Value of mExpandImmediate = " + mExpandImmediate);
+        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -933,7 +939,6 @@
         return mShadeExpandedHeight;
     }
 
-    @VisibleForTesting
     void setExpandImmediate(boolean expandImmediate) {
         if (expandImmediate != mExpandImmediate) {
             mShadeLog.logQsExpandImmediateChanged(expandImmediate);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 22c63817..d7a3392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -27,6 +27,8 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -56,6 +58,7 @@
 
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
+    private final LogBuffer mTouchLog;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
@@ -79,6 +82,7 @@
     public ShadeControllerImpl(
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
+            @ShadeTouchLog LogBuffer touchLog,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -92,6 +96,7 @@
     ) {
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
+        mTouchLog = touchLog;
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
         mStatusBarWindowController = statusBarWindowController;
@@ -413,6 +418,7 @@
 
     @Override
     public void start() {
+        TouchLogger.logTouchesTo(mTouchLog);
         getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
         getShadeViewController().setOpenCloseListener(
                 new OpenCloseListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index c6cb9c4..bea12de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -130,12 +130,12 @@
     private lateinit var carrierIconSlots: List<String>
     private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
 
-    private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-    private val clock: Clock = header.findViewById(R.id.clock)
-    private val date: TextView = header.findViewById(R.id.date)
-    private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
-    private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
-    private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
+    private val batteryIcon: BatteryMeterView = header.requireViewById(R.id.batteryRemainingIcon)
+    private val clock: Clock = header.requireViewById(R.id.clock)
+    private val date: TextView = header.requireViewById(R.id.date)
+    private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
+    private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
+    private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -582,7 +582,7 @@
     inner class CustomizerAnimationListener(
         private val enteringCustomizing: Boolean,
     ) : AnimatorListenerAdapter() {
-        override fun onAnimationEnd(animation: Animator?) {
+        override fun onAnimationEnd(animation: Animator) {
             super.onAnimationEnd(animation)
             header.animate().setListener(null)
             if (enteringCustomizing) {
@@ -590,7 +590,7 @@
             }
         }
 
-        override fun onAnimationStart(animation: Animator?) {
+        override fun onAnimationStart(animation: Animator) {
             super.onAnimationStart(animation)
             if (!enteringCustomizing) {
                 customizing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1c30bdd..8d23f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -79,19 +79,39 @@
 
     fun logMotionEvent(event: MotionEvent, message: String) {
         buffer.log(
-            TAG,
-            LogLevel.VERBOSE,
-            {
-                str1 = message
-                long1 = event.eventTime
-                long2 = event.downTime
-                int1 = event.action
-                int2 = event.classification
-                double1 = event.y.toDouble()
-            },
-            {
-                "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
-            }
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                    int1 = event.action
+                    int2 = event.classification
+                },
+                {
+                    "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+                }
+        )
+    }
+
+    /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+    fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+        buffer.log(
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                },
+                {
+                    val prefix = when (result) {
+                        true -> "SHADE TOUCH REROUTED"
+                        false -> "SHADE TOUCH BLOCKED"
+                        null -> "SHADE TOUCH DISPATCHED"
+                    }
+                    "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+                }
         )
     }
 
@@ -316,6 +336,17 @@
         )
     }
 
+    fun logPanelStateChanged(@PanelState panelState: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = panelState.panelStateToString()
+            },
+            { "New panel State: $str1" }
+        )
+    }
+
     fun flingQs(flingType: Int, isClick: Boolean) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 6e76784..05b1ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -106,7 +106,7 @@
             featureFlags: FeatureFlags,
         ): NotificationShadeWindowView {
             if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-                return root.findViewById(R.id.legacy_window_root)
+                return root.requireViewById(R.id.legacy_window_root)
             }
             return root as NotificationShadeWindowView?
                 ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
@@ -118,7 +118,7 @@
         fun providesNotificationStackScrollLayout(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationStackScrollLayout {
-            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+            return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
         }
 
         @Provides
@@ -153,7 +153,7 @@
         fun providesNotificationPanelView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationPanelView {
-            return notificationShadeWindowView.findViewById(R.id.notification_panel)
+            return notificationShadeWindowView.requireViewById(R.id.notification_panel)
         }
 
         /**
@@ -175,7 +175,7 @@
         fun providesLightRevealScrim(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): LightRevealScrim {
-            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+            return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
         }
 
         @Provides
@@ -183,7 +183,7 @@
         fun providesKeyguardRootView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): KeyguardRootView {
-            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+            return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
         }
 
         @Provides
@@ -191,7 +191,7 @@
         fun providesSharedNotificationContainer(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): SharedNotificationContainer {
-            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+            return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -200,7 +200,7 @@
         fun providesAuthRippleView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): AuthRippleView? {
-            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+            return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -212,9 +212,9 @@
             featureFlags: FeatureFlags
         ): LockIconView {
             if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
-                return keyguardRootView.findViewById(R.id.lock_icon_view)
+                return keyguardRootView.requireViewById(R.id.lock_icon_view)
             } else {
-                return notificationPanelView.findViewById(R.id.lock_icon_view)
+                return notificationPanelView.requireViewById(R.id.lock_icon_view)
             }
         }
 
@@ -224,7 +224,7 @@
         fun providesTapAgainView(
             notificationPanelView: NotificationPanelView,
         ): TapAgainView {
-            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+            return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -233,7 +233,7 @@
         fun providesNotificationsQuickSettingsContainer(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationsQuickSettingsContainer {
-            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+            return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -243,7 +243,7 @@
         fun providesShadeHeaderView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): MotionLayout {
-            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+            val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
             val layoutId = R.layout.combined_qs_header
             stub.layoutResource = layoutId
             return stub.inflate() as MotionLayout
@@ -260,7 +260,7 @@
         @SysUISingleton
         @Named(SHADE_HEADER)
         fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
-            return view.findViewById(R.id.batteryRemainingIcon)
+            return view.requireViewById(R.id.batteryRemainingIcon)
         }
 
         @Provides
@@ -295,7 +295,7 @@
         fun providesOngoingPrivacyChip(
             @Named(SHADE_HEADER) header: MotionLayout,
         ): OngoingPrivacyChip {
-            return header.findViewById(R.id.privacy_chip)
+            return header.requireViewById(R.id.privacy_chip)
         }
 
         @Provides
@@ -304,7 +304,7 @@
         fun providesStatusIconContainer(
             @Named(SHADE_HEADER) header: MotionLayout,
         ): StatusIconContainer {
-            return header.findViewById(R.id.statusIcons)
+            return header.requireViewById(R.id.statusIcons)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 0000000..58704bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+    companion object {
+        private var touchLogger: DispatchTouchLogger? = null
+
+        @JvmStatic
+        fun logTouchesTo(buffer: LogBuffer) {
+            touchLogger = DispatchTouchLogger(buffer)
+        }
+
+        @JvmStatic
+        fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+            touchLogger?.logDispatchTouch(viewTag, ev, result)
+            return result
+        }
+    }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+    fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+        // NOTE: never log position of touches for security purposes
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = viewTag
+                int1 = ev.action
+                long1 = ev.downTime
+                bool1 = result
+            },
+            { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+        )
+    }
+
+    private fun typeToString(type: Int): String {
+        return when (type) {
+            MotionEvent.ACTION_DOWN -> "DOWN"
+            MotionEvent.ACTION_UP -> "UP"
+            MotionEvent.ACTION_MOVE -> "MOVE"
+            MotionEvent.ACTION_CANCEL -> "CANCEL"
+            MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+            MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+            else -> "OTHER"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 87abc92..8edc26d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -25,7 +25,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the shade scene. */
@@ -39,14 +39,22 @@
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
-        authenticationInteractor.isUnlocked
-            .map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) }
+        combine(
+                authenticationInteractor.isUnlocked,
+                authenticationInteractor.canSwipeToDismiss,
+            ) { isUnlocked, canSwipeToDismiss ->
+                upDestinationSceneKey(
+                    isUnlocked = isUnlocked,
+                    canSwipeToDismiss = canSwipeToDismiss,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     upDestinationSceneKey(
                         isUnlocked = authenticationInteractor.isUnlocked.value,
+                        canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
                     ),
             )
 
@@ -57,7 +65,12 @@
 
     private fun upDestinationSceneKey(
         isUnlocked: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen
+        return when {
+            canSwipeToDismiss -> SceneKey.Lockscreen
+            isUnlocked -> SceneKey.Gone
+            else -> SceneKey.Lockscreen
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 37140ec..5209767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -37,8 +37,8 @@
 
     init {
         inflate(context, R.layout.battery_status_chip, this)
-        roundedContainer = findViewById(R.id.rounded_container)
-        batteryMeterView = findViewById(R.id.battery_meter_view)
+        roundedContainer = requireViewById(R.id.rounded_container)
+        batteryMeterView = requireViewById(R.id.battery_meter_view)
         updateResources()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..3120128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@
 import android.os.Trace
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
+import android.view.MotionEvent
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@
     }
 }
 
+private const val TAG = "LightRevealScrim"
+
 /**
  * Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
  * transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -419,15 +423,14 @@
         revealGradientCenter.y = top + (revealGradientHeight / 2f)
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         if (
-            canvas == null ||
-                revealGradientWidth <= 0 ||
-                revealGradientHeight <= 0 ||
-                revealAmount == 0f
+            revealGradientWidth <= 0 ||
+            revealGradientHeight <= 0 ||
+            revealAmount == 0f
         ) {
             if (revealAmount < 1f) {
-                canvas?.drawColor(revealGradientEndColor)
+                canvas.drawColor(revealGradientEndColor)
             }
             return
         }
@@ -447,6 +450,10 @@
         canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+    }
+
     private fun setPaintColorFilter() {
         gradientPaint.colorFilter =
             PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4710574..672796a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -474,7 +474,7 @@
         }
         if (endlistener != null) {
             dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     endlistener.invoke()
                 }
             })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 750272d..17b4e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -66,7 +66,7 @@
                 inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
                 oldIn.recycle()
             }
-            val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
                     Bitmap.Config.ARGB_8888)
 
             input = Allocation.createFromBitmap(renderScript, inBitmap,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b624115..59c63aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -272,7 +272,7 @@
                             blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         keyguardAnimator = null
                         wakeAndUnlockBlurRadius = 0f
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eddb683..d1e0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -234,7 +234,7 @@
             }
 
             // Set the dot's view gravity to hug the status bar
-            (corner.findViewById<View>(R.id.privacy_dot)
+            (corner.requireViewById<View>(R.id.privacy_dot)
                     .layoutParams as FrameLayout.LayoutParams)
                         .gravity = rotatedCorner.innerGravity()
         }
@@ -255,7 +255,7 @@
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
         val size = Point()
-        tl.context.display.getRealSize(size)
+        tl.context.display?.getRealSize(size)
         val currentRotation = RotationUtils.getExactRotation(tl.context)
 
         val displayWidth: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 6e8b8bd..1ad4620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -168,10 +168,8 @@
         }
 
         val keyFrame1Height = dotSize * 2
-        val v = currentAnimatedView!!.view
-        val chipVerticalCenter = v.top + v.measuredHeight / 2
-        val height1 = ValueAnimator.ofInt(
-                currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
+        val chipVerticalCenter = chipBounds.top + chipBounds.height() / 2
+        val height1 = ValueAnimator.ofInt(chipBounds.height(), keyFrame1Height).apply {
             startDelay = 8.frames
             duration = 6.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 23edf17..2403920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -164,7 +164,9 @@
         }
 
         private fun isChipAnimationEnabled(): Boolean {
-            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, true)
+            val defaultValue =
+                context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation)
+            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, defaultValue)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index e018397..f40f570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -243,7 +243,7 @@
         if (!event.showAnimation && event.forceVisible) {
             // If animations are turned off, we'll transition directly to the dot
             animationState.value = SHOWING_PERSISTENT_DOT
-            notifyTransitionToPersistentDot()
+            notifyTransitionToPersistentDot(event)
             return
         }
 
@@ -335,7 +335,7 @@
         }
         animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
         if (hasPersistentDot) {
-            val dotAnim = notifyTransitionToPersistentDot()
+            val dotAnim = notifyTransitionToPersistentDot(currentlyDisplayedEvent)
             if (dotAnim != null) {
                 animators.add(dotAnim)
             }
@@ -344,12 +344,12 @@
         return AnimatorSet().also { it.playTogether(animators) }
     }
 
-    private fun notifyTransitionToPersistentDot(): Animator? {
+    private fun notifyTransitionToPersistentDot(event: StatusEvent?): Animator? {
         logger?.logTransitionToPersistentDotCallbackInvoked()
         val anims: List<Animator> =
             listeners.mapNotNull {
                 it.onSystemStatusAnimationTransitionToPersistentDot(
-                    currentlyDisplayedEvent?.contentDescription
+                    event?.contentDescription
                 )
             }
         if (anims.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index efd7d2e..6346111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -179,15 +179,20 @@
         }
         if (weatherTarget != null) {
             val clickIntent = weatherTarget.headerAction?.intent
-            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras, { v ->
-                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                    activityStarter.startActivity(
-                        clickIntent,
-                        true, /* dismissShade */
-                        null,
-                        false)
+            val weatherData = weatherTarget.baseAction?.extras?.let { extras ->
+                WeatherData.fromBundle(
+                    extras,
+                ) { _ ->
+                    if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                        activityStarter.startActivity(
+                            clickIntent,
+                            true, /* dismissShade */
+                            null,
+                            false)
+                    }
                 }
-            })
+            }
+
             if (weatherData != null) {
                 keyguardUpdateMonitor.sendWeatherData(weatherData)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..1b43922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -74,7 +74,7 @@
                     root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         endRunnable?.run()
                     }
                 })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
deleted file mode 100644
index f04b24e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ /dev/null
@@ -1,130 +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.coordinator;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Handles ForegroundService and AppOp interactions with notifications.
- *  Tags notifications with appOps
- *  Lifetime extends notifications associated with an ongoing ForegroundService.
- *  Filters out notifications that represent foreground services that are no longer running
- *  Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
- *      section ordering priority.
- *
- * Previously this logic lived in
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
- */
-@CoordinatorScope
-public class AppOpsCoordinator implements Coordinator {
-    private static final String TAG = "AppOpsCoordinator";
-
-    private final ForegroundServiceController mForegroundServiceController;
-    private final AppOpsController mAppOpsController;
-    private final DelayableExecutor mMainExecutor;
-
-    private NotifPipeline mNotifPipeline;
-
-    @Inject
-    public AppOpsCoordinator(
-            ForegroundServiceController foregroundServiceController,
-            AppOpsController appOpsController,
-            @Main DelayableExecutor mainExecutor) {
-        mForegroundServiceController = foregroundServiceController;
-        mAppOpsController = appOpsController;
-        mMainExecutor = mainExecutor;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-        mNotifPipeline = pipeline;
-
-        // filter out foreground service notifications that aren't necessary anymore
-        mNotifPipeline.addPreGroupFilter(mNotifFilter);
-
-    }
-
-    public NotifSectioner getSectioner() {
-        return mNotifSectioner;
-    }
-
-    /**
-     * Filters out notifications that represent foreground services that are no longer running or
-     * that already have an app notification with the appOps tagged to
-     */
-    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
-        @Override
-        public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            StatusBarNotification sbn = entry.getSbn();
-
-            // Filters out system-posted disclosure notifications when unneeded
-            if (mForegroundServiceController.isDisclosureNotification(sbn)
-                    && !mForegroundServiceController.isDisclosureNeededForUser(
-                            sbn.getUser().getIdentifier())) {
-                return true;
-            }
-            return false;
-        }
-    };
-
-    /**
-     * Puts colorized foreground service and call notifications into its own section.
-     */
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
-            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
-        @Override
-        public boolean isInSection(ListEntry entry) {
-            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
-            if (notificationEntry != null) {
-                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
-            }
-            return false;
-        }
-
-        private boolean isColorizedForegroundService(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return notification.isForegroundService()
-                    && notification.isColorized()
-                    && entry.getImportance() > IMPORTANCE_MIN;
-        }
-
-        private boolean isCall(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return entry.getImportance() > IMPORTANCE_MIN
-                    && notification.isStyle(Notification.CallStyle.class);
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
new file mode 100644
index 0000000..63997f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.coordinator;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import javax.inject.Inject;
+
+/**
+ * Handles sectioning for foreground service notifications.
+ *  Puts non-min colorized foreground service notifications into the FGS section. See
+ *  {@link NotifCoordinators} for section ordering priority.
+ */
+@CoordinatorScope
+public class ColorizedFgsCoordinator implements Coordinator {
+    private static final String TAG = "ColorizedCoordinator";
+
+    @Inject
+    public ColorizedFgsCoordinator() {
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+    }
+
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
+    }
+
+
+    /**
+     * Puts colorized foreground service and call notifications into its own section.
+     */
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
+            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
+        @Override
+        public boolean isInSection(ListEntry entry) {
+            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+            if (notificationEntry != null) {
+                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
+            }
+            return false;
+        }
+
+        private boolean isColorizedForegroundService(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return notification.isForegroundService()
+                    && notification.isColorized()
+                    && entry.getImportance() > IMPORTANCE_MIN;
+        }
+
+        private boolean isCall(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return entry.getImportance() > IMPORTANCE_MIN
+                    && notification.isStyle(Notification.CallStyle.class);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 0ccab9e..226a957 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -33,34 +33,34 @@
 
 @CoordinatorScope
 class NotifCoordinatorsImpl @Inject constructor(
-    sectionStyleProvider: SectionStyleProvider,
-    featureFlags: FeatureFlags,
-    dataStoreCoordinator: DataStoreCoordinator,
-    hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
-    hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
-    keyguardCoordinator: KeyguardCoordinator,
-    rankingCoordinator: RankingCoordinator,
-    appOpsCoordinator: AppOpsCoordinator,
-    deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
-    bubbleCoordinator: BubbleCoordinator,
-    headsUpCoordinator: HeadsUpCoordinator,
-    gutsCoordinator: GutsCoordinator,
-    conversationCoordinator: ConversationCoordinator,
-    debugModeCoordinator: DebugModeCoordinator,
-    groupCountCoordinator: GroupCountCoordinator,
-    groupWhenCoordinator: GroupWhenCoordinator,
-    mediaCoordinator: MediaCoordinator,
-    preparationCoordinator: PreparationCoordinator,
-    remoteInputCoordinator: RemoteInputCoordinator,
-    rowAppearanceCoordinator: RowAppearanceCoordinator,
-    stackCoordinator: StackCoordinator,
-    shadeEventCoordinator: ShadeEventCoordinator,
-    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
-    viewConfigCoordinator: ViewConfigCoordinator,
-    visualStabilityCoordinator: VisualStabilityCoordinator,
-    sensitiveContentCoordinator: SensitiveContentCoordinator,
-    dismissibilityCoordinator: DismissibilityCoordinator,
-    dreamCoordinator: DreamCoordinator,
+        sectionStyleProvider: SectionStyleProvider,
+        featureFlags: FeatureFlags,
+        dataStoreCoordinator: DataStoreCoordinator,
+        hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+        hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+        keyguardCoordinator: KeyguardCoordinator,
+        rankingCoordinator: RankingCoordinator,
+        colorizedFgsCoordinator: ColorizedFgsCoordinator,
+        deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+        bubbleCoordinator: BubbleCoordinator,
+        headsUpCoordinator: HeadsUpCoordinator,
+        gutsCoordinator: GutsCoordinator,
+        conversationCoordinator: ConversationCoordinator,
+        debugModeCoordinator: DebugModeCoordinator,
+        groupCountCoordinator: GroupCountCoordinator,
+        groupWhenCoordinator: GroupWhenCoordinator,
+        mediaCoordinator: MediaCoordinator,
+        preparationCoordinator: PreparationCoordinator,
+        remoteInputCoordinator: RemoteInputCoordinator,
+        rowAppearanceCoordinator: RowAppearanceCoordinator,
+        stackCoordinator: StackCoordinator,
+        shadeEventCoordinator: ShadeEventCoordinator,
+        smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+        viewConfigCoordinator: ViewConfigCoordinator,
+        visualStabilityCoordinator: VisualStabilityCoordinator,
+        sensitiveContentCoordinator: SensitiveContentCoordinator,
+        dismissibilityCoordinator: DismissibilityCoordinator,
+        dreamCoordinator: DreamCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -79,7 +79,7 @@
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
         mCoordinators.add(rankingCoordinator)
-        mCoordinators.add(appOpsCoordinator)
+        mCoordinators.add(colorizedFgsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
         mCoordinators.add(debugModeCoordinator)
@@ -106,7 +106,7 @@
 
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
-        mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
         mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
         mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 106d11f..7d1cca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -70,7 +69,6 @@
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
-    private val fgsNotifListener: ForegroundServiceNotificationListener,
     private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
@@ -105,7 +103,6 @@
         notificationsMediaManager.setUpWithPresenter(presenter)
         notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
-        fgsNotifListener.init()
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 38a1579..9c4aa07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -60,7 +60,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        appControlRow = findViewById(R.id.app_control)
+        appControlRow = requireViewById(R.id.app_control)
     }
 
     /**
@@ -143,9 +143,9 @@
     lateinit var switch: Switch
 
     override fun onFinishInflate() {
-        iconView = findViewById(R.id.icon)
-        channelName = findViewById(R.id.app_name)
-        switch = findViewById(R.id.toggle)
+        iconView = requireViewById(R.id.icon)
+        channelName = requireViewById(R.id.app_name)
+        switch = requireViewById(R.id.toggle)
 
         setOnClickListener { switch.toggle() }
     }
@@ -174,9 +174,9 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        channelName = findViewById(R.id.channel_name)
-        channelDescription = findViewById(R.id.channel_description)
-        switch = findViewById(R.id.toggle)
+        channelName = requireViewById(R.id.channel_name)
+        channelDescription = requireViewById(R.id.channel_description)
+        switch = requireViewById(R.id.toggle)
         switch.setOnCheckedChangeListener { _, b ->
             channel?.let {
                 controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
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 ed489a6c..d92d11b 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
@@ -1894,6 +1894,9 @@
             return traceTag;
         }
 
+        if (isSummaryWithChildren()) {
+            return traceTag + "(summary)";
+        }
         Class<? extends Notification.Style> style =
                 getEntry().getSbn().getNotification().getNotificationStyle();
         if (style == null) {
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 d71bc2f..5e3a67e 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
@@ -93,6 +93,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -3480,6 +3481,11 @@
         return super.onTouchEvent(ev);
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     void dispatchDownEventToScroller(MotionEvent ev) {
         MotionEvent downEvent = MotionEvent.obtain(ev);
         downEvent.setAction(MotionEvent.ACTION_DOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 2ccbc9f..baeae79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -914,8 +914,8 @@
             val packages: Array<String> =
                 context.resources.getStringArray(R.array.system_ui_packages)
             for (pkg in packages) {
-                if (intent.component == null) break
-                if (pkg == intent.component.packageName) {
+                val componentName = intent.component ?: break
+                if (pkg == componentName.packageName) {
                     return UserHandle(UserHandle.myUserId())
                 }
             }
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 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
 
     private final ActivityIntentHelper mActivityIntentHelper;
 
-    private final NotificationStackScrollLayoutController mStackScrollerController;
+    public final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
 import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
 import javax.inject.Inject
 
 @SysUISingleton
@@ -40,6 +38,7 @@
     private var localeList: LocaleList? = null
     private val context: Context
     private var layoutDirection: Int
+    private var orientation = Configuration.ORIENTATION_UNDEFINED
 
     init {
         val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
                 it.onThemeChanged()
             }
         }
+
+        val newOrientation = newConfig.orientation
+        if (orientation != newOrientation) {
+            orientation = newOrientation
+            listeners.filterForEach({ this.listeners.contains(it) }) {
+                it.onOrientationChanged(orientation)
+            }
+        }
     }
 
+
+
     override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
         listeners.add(listener)
         listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
     private final KeyguardBypassController mBypassController;
     private final StatusBarStateController mStatusBarStateController;
+    private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private final CommandQueue mCommandQueue;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
 
@@ -109,6 +110,7 @@
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController stateController,
+            PhoneStatusBarTransitions phoneStatusBarTransitions,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
         });
         mBypassController = bypassController;
         mStatusBarStateController = stateController;
+        mPhoneStatusBarTransitions = phoneStatusBarTransitions;
         mWakeUpCoordinator = wakeUpCoordinator;
         mCommandQueue = commandQueue;
         mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
     @Override
     public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
         updateHeadsUpAndPulsingRoundness(entry);
+        mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
     }
 
     private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 34bbd13..cdd410e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.animation.requiresRemeasuring
 
 /**
  * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -98,7 +99,7 @@
         ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
             // remove old ambient indication from its parent
             val originalAmbientIndicationView =
-                oldBottomArea.findViewById<View>(R.id.ambient_indication_container)
+                oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
             (originalAmbientIndicationView.parent as ViewGroup).removeView(
                 originalAmbientIndicationView
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 117a1a4..914e0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -87,6 +87,7 @@
     private int mStatusBarPaddingEnd;
     private int mMinDotWidth;
     private View mSystemIconsContainer;
+    private View mSystemIcons;
     private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
             DarkChange.EMPTY);
 
@@ -119,6 +120,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
+        mSystemIcons = findViewById(R.id.system_icons);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -167,6 +169,13 @@
                 mStatusIconContainer.getPaddingBottom()
         );
 
+        mSystemIcons.setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
+
         // Respect font size setting.
         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                 getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
 
     private final float mIconAlphaWhenOpaque;
 
+    private boolean mIsHeadsUp;
+
     private View mStartSide, mStatusIcons, mBattery;
     private Animator mCurrentAnimation;
 
@@ -52,15 +54,32 @@
         return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
     }
 
-    private float getNonBatteryClockAlphaFor(int mode) {
-        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
-                : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
-                : mIconAlphaWhenOpaque;
+    private float getStatusIconsAlphaFor(int mode) {
+        return getDefaultAlphaFor(mode);
+    }
+
+    private float getStartSideAlphaFor(int mode) {
+        // When there's a heads up notification, we need the start side icons to show regardless of
+        // lights out mode.
+        if (mIsHeadsUp) {
+            return getIconAlphaBasedOnOpacity(mode);
+        }
+        return getDefaultAlphaFor(mode);
     }
 
     private float getBatteryClockAlpha(int mode) {
         return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
-                : getNonBatteryClockAlphaFor(mode);
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getDefaultAlphaFor(int mode) {
+        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getIconAlphaBasedOnOpacity(int mode) {
+        return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+                : mIconAlphaWhenOpaque;
     }
 
     private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
         applyMode(newMode, animate);
     }
 
+    /** Informs this controller that the heads up notification state has changed. */
+    public void onHeadsUpStateChanged(boolean isHeadsUp) {
+        mIsHeadsUp = isHeadsUp;
+        // We want the icon to be fully visible when the HUN appears, so just immediately change the
+        // icon visibility and don't animate.
+        applyMode(getMode(), /* animate= */ false);
+    }
+
     private void applyMode(int mode, boolean animate) {
         if (mStartSide == null) return; // pre-init
-        float newAlpha = getNonBatteryClockAlphaFor(mode);
-        float newAlphaBC = getBatteryClockAlpha(mode);
+        float newStartSideAlpha = getStartSideAlphaFor(mode);
+        float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+        float newBatteryAlpha = getBatteryClockAlpha(mode);
         if (mCurrentAnimation != null) {
             mCurrentAnimation.cancel();
         }
         if (animate) {
             AnimatorSet anims = new AnimatorSet();
             anims.playTogether(
-                    animateTransitionTo(mStartSide, newAlpha),
-                    animateTransitionTo(mStatusIcons, newAlpha),
-                    animateTransitionTo(mBattery, newAlphaBC)
+                    animateTransitionTo(mStartSide, newStartSideAlpha),
+                    animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+                    animateTransitionTo(mBattery, newBatteryAlpha)
                     );
             if (isLightsOut(mode)) {
                 anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
             anims.start();
             mCurrentAnimation = anims;
         } else {
-            mStartSide.setAlpha(newAlpha);
-            mStatusIcons.setAlpha(newAlpha);
-            mBattery.setAlpha(newAlphaBC);
+            mStartSide.setAlpha(newStartSideAlpha);
+            mStatusIcons.setAlpha(newStatusIconsAlpha);
+            mBattery.setAlpha(newBatteryAlpha);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d546a84..83a040c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -208,25 +208,29 @@
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         layoutParams.height = mStatusBarHeight - waterfallTopInset;
+        updatePaddings();
+        setLayoutParams(layoutParams);
+    }
 
-        int statusBarPaddingTop = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_top);
+    private void updatePaddings() {
         int statusBarPaddingStart = getResources().getDimensionPixelSize(
                 R.dimen.status_bar_padding_start);
-        int statusBarPaddingEnd = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_end);
 
-        View sbContents = findViewById(R.id.status_bar_contents);
-        sbContents.setPaddingRelative(
+        findViewById(R.id.status_bar_contents).setPaddingRelative(
                 statusBarPaddingStart,
-                statusBarPaddingTop,
-                statusBarPaddingEnd,
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
                 0);
 
         findViewById(R.id.notification_lights_out)
                 .setPaddingRelative(0, statusBarPaddingStart, 0, 0);
 
-        setLayoutParams(layoutParams);
+        findViewById(R.id.system_icons).setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
     }
 
     private void updateLayoutForCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 2affb817..931aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -75,13 +75,13 @@
     }
 
     override fun onViewAttached() {
-        statusContainer = mView.findViewById(R.id.system_icons)
+        statusContainer = mView.requireViewById(R.id.system_icons)
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
         if (moveFromCenterAnimationController == null) return
 
-        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
-        val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
+        val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+        val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
 
         val viewsToAnimate = arrayOf(
             statusBarLeftSide,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e82ac59..fc66138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -370,6 +370,9 @@
         mScrimBehind = behindScrim;
         mScrimInFront = scrimInFront;
         updateThemeColors();
+        mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+        mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+        mScrimInFront.setScrimName(getScrimName(mScrimInFront));
 
         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index c850d4f..ad18170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -117,11 +117,11 @@
      * status bar area is contiguous.
      */
     fun currentRotationHasCornerCutout(): Boolean {
-        val cutout = context.display.cutout ?: return false
+        val cutout = checkNotNull(context.display).cutout ?: return false
         val topBounds = cutout.boundingRectTop
 
         val point = Point()
-        context.display.getRealSize(point)
+        checkNotNull(context.display).getRealSize(point)
 
         return topBounds.left <= 0 || topBounds.right >= point.x
     }
@@ -161,7 +161,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val displayCutout = context.display.cutout
+            val displayCutout = checkNotNull(context.display).cutout
             val key = getCacheKey(rotation, displayCutout)
 
             val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -198,7 +198,7 @@
     fun getStatusBarContentAreaForRotation(
         @Rotation rotation: Int
     ): Rect {
-        val displayCutout = context.display.cutout
+        val displayCutout = checkNotNull(context.display).cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
                 rotation, displayCutout, getResourcesForRotation(rotation, context), key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -97,6 +101,9 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+
+    private FeatureFlags mFlags;
+
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
             }
         }
     };
+    private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            @Main CoroutineDispatcher mainDispatcher,
+            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
+        mFlags = featureFlags;
         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mMainDispatcher = mainDispatcher;
+        mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
     }
 
+    KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    CoroutineDispatcher mMainDispatcher;
+
     @Override
     public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
         }
     }
 
+    private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onUnlockedChanged() {
+                    updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+                }
+            };
+
     private void registerListeners() {
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mShadeViewController.postToView(() ->
+                    collectFlow(
+                        getViewRootImpl().getView(),
+                        combineFlows(
+                                mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+                                mWmLockscreenVisibilityInteractor.get()
+                                        .getUsingKeyguardGoingAwayAnimation(),
+                                (finishedState, animating) ->
+                                        KeyguardInteractor.Companion.isKeyguardVisibleInState(
+                                                finishedState)
+                                                || animating),
+                        this::consumeShowStatusBarKeyguardView));
+        }
+    }
+
+    private void consumeShowStatusBarKeyguardView(boolean show) {
+        if (show != mLastShowing) {
+            if (show) {
+                show(null);
+            } else {
+                hide(0, 0);
+            }
+        }
     }
 
     /** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e8da951..1bceb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -105,18 +105,18 @@
             }
         }
         addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationCancel(animation: Animator?) {
+            override fun onAnimationCancel(animation: Animator) {
                 if (lightRevealScrim.revealEffect !is CircleReveal) {
                     lightRevealScrim.revealAmount = 1f
                 }
             }
 
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 lightRevealAnimationPlaying = false
                 interactionJankMonitor.end(CUJ_SCREEN_OFF)
             }
 
-            override fun onAnimationStart(animation: Animator?) {
+            override fun onAnimationStart(animation: Animator) {
                 interactionJankMonitor.begin(
                         notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
             }
@@ -345,7 +345,7 @@
         // portrait. If we're in another orientation, disable the screen off animation so we don't
         // animate in the keyguard AOD UI sideways or upside down.
         if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
-            context.display.rotation != Surface.ROTATION_0) {
+            context.display?.rotation != Surface.ROTATION_0) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 270c592..1259477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -34,7 +34,7 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        text = findViewById(R.id.current_user_name)
-        avatar = findViewById(R.id.current_user_avatar)
+        text = requireViewById(R.id.current_user_name)
+        avatar = requireViewById(R.id.current_user_avatar)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
         default void onThemeChanged() {}
         default void onLocaleListChanged() {}
         default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+        default void onOrientationChanged(int orientation) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 4950482..ffb743f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -128,7 +128,8 @@
 
         val prefs = userContextProvider.userContext.getSharedPreferences(
             PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
 
         val controlsController = controlsComponent.getControlsController().get()
         val componentsToSeed = mutableListOf<ComponentName>()
@@ -174,7 +175,8 @@
     }
 
     private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
         val updatedPkgs = seededPackages.toMutableSet()
         updatedPkgs.add(pkg)
         prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 710588c..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -18,6 +18,8 @@
 
 import static android.hardware.biometrics.BiometricSourceType.FACE;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -38,6 +40,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -49,6 +52,7 @@
 import javax.inject.Inject;
 
 /**
+ *
  */
 @SysUISingleton
 public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -103,7 +107,10 @@
      */
     private boolean mSnappingKeyguardBackAfterSwipe = false;
 
+    private FeatureFlags mFeatureFlags;
+
     /**
+     *
      */
     @Inject
     public KeyguardStateControllerImpl(
@@ -112,13 +119,15 @@
             LockPatternUtils lockPatternUtils,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mLogger = logger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+        mFeatureFlags = featureFlags;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -272,7 +281,8 @@
     @Override
     public boolean isKeyguardScreenRotationAllowed() {
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
-                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+                || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 27aaa68..eb7d339 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -353,8 +353,8 @@
                     // before CoreStartables run, and will not be removed.
                     // In many cases, it reports the battery level of the stylus.
                     registerBatteryListener(deviceId)
-                } else if (device.bluetoothAddress != null) {
-                    onStylusBluetoothConnected(deviceId, device.bluetoothAddress)
+                } else {
+                    device.bluetoothAddress?.let { onStylusBluetoothConnected(deviceId, it) }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
index 72786ef..5ad9630 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -60,7 +60,7 @@
 
     override fun getWidth(): Int {
         val displayMetrics = context.resources.displayMetrics.apply {
-            context.display.getRealMetrics(this)
+            checkNotNull(context.display).getRealMetrics(this)
         }
         return displayMetrics.widthPixels
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 088cd93..ee84580 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -52,22 +52,22 @@
     override fun show() {
         // need to call show() first in order to construct the listView
         super.show()
-        val listView = getListView()
+        listView?.apply {
+            isVerticalScrollBarEnabled = false
+            isHorizontalScrollBarEnabled = false
 
-        listView.setVerticalScrollBarEnabled(false)
-        listView.setHorizontalScrollBarEnabled(false)
+            // Creates a transparent spacer between items
+            val shape = ShapeDrawable()
+            shape.alpha = 0
+            divider = shape
+            dividerHeight = res.getDimensionPixelSize(
+                R.dimen.bouncer_user_switcher_popup_divider_height)
 
-        // Creates a transparent spacer between items
-        val shape = ShapeDrawable()
-        shape.setAlpha(0)
-        listView.setDivider(shape)
-        listView.setDividerHeight(res.getDimensionPixelSize(
-            R.dimen.bouncer_user_switcher_popup_divider_height))
-
-        val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
-        listView.addHeaderView(createSpacer(height), null, false)
-        listView.addFooterView(createSpacer(height), null, false)
-        setWidth(findMaxWidth(listView))
+            val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+            addHeaderView(createSpacer(height), null, false)
+            addFooterView(createSpacer(height), null, false)
+            setWidth(findMaxWidth(this))
+        }
 
         super.show()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f026f0f..bfed0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -227,7 +227,8 @@
                     )
                 }
                 try {
-                    WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+                    checkNotNull(WindowManagerGlobal.getWindowManagerService())
+                        .lockNow(/* options= */ null)
                 } catch (e: RemoteException) {
                     Log.e(
                         TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 00ca92d..1ac86ce 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -67,7 +67,7 @@
         val resourceId: Int? = getGuestUserRecordNameResourceId(record)
         return when {
             resourceId != null -> context.getString(resourceId)
-            record.info != null -> record.info.name
+            record.info != null -> checkNotNull(record.info.name)
             else ->
                 context.getString(
                     getUserSwitcherActionTextResourceId(
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 56c5d3b..7866d76 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -223,11 +223,11 @@
         }
     }
 
-    override fun dispatchDraw(canvas: Canvas?) {
-        canvas?.save()
-        canvas?.clipRect(boundsRect)
+    override fun dispatchDraw(canvas: Canvas) {
+        canvas.save()
+        canvas.clipRect(boundsRect)
         super.dispatchDraw(canvas)
-        canvas?.restore()
+        canvas.restore()
     }
 
     private fun updateBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+    return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index cb18229..9f7ab7b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -150,7 +150,7 @@
     private fun getPatternTopGuideline(): Float {
         val cs = ConstraintSet()
         val container =
-            mKeyguardPatternView.findViewById(R.id.pattern_container) as ConstraintLayout
+            mKeyguardPatternView.requireViewById(R.id.pattern_container) as ConstraintLayout
         cs.clone(container)
         return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4dc7652..309d9e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -114,7 +114,7 @@
 
         objectKeyguardPINView =
             View.inflate(mContext, R.layout.keyguard_pin_view, null)
-                .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+                .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
     }
 
     private fun constructPinViewController(
@@ -175,7 +175,7 @@
 
     private fun getPinTopGuideline(): Float {
         val cs = ConstraintSet()
-        val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+        val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
         cs.clone(container)
         return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9ba21da..0192e78 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -38,6 +38,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -46,6 +47,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
@@ -144,6 +147,8 @@
     private lateinit var testableResources: TestableResources
     private lateinit var sceneTestUtils: SceneTestUtils
     private lateinit var sceneInteractor: SceneInteractor
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var authenticationInteractor: AuthenticationInteractor
     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
 
     private lateinit var underTest: KeyguardSecurityContainerController
@@ -182,6 +187,7 @@
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.SCENE_CONTAINER, false)
         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -204,9 +210,17 @@
         whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
         sceneTestUtils = SceneTestUtils(this)
         sceneInteractor = sceneTestUtils.sceneInteractor()
+        keyguardTransitionInteractor =
+            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+                .keyguardTransitionInteractor
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+        authenticationInteractor =
+            sceneTestUtils.authenticationInteractor(
+                repository = sceneTestUtils.authenticationRepository(),
+                sceneInteractor = sceneInteractor
+            )
 
         underTest =
             KeyguardSecurityContainerController(
@@ -236,8 +250,9 @@
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
                 userInteractor,
                 faceAuthAccessibilityDelegate,
+                keyguardTransitionInteractor
             ) {
-                sceneInteractor
+                authenticationInteractor
             }
     }
 
@@ -493,30 +508,6 @@
     }
 
     @Test
-    fun showNextSecurityScreenOrFinish_SimPinToAnotherSimPin_None() {
-        // GIVEN the current security method is SimPin
-        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
-        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
-            .thenReturn(false)
-        underTest.showSecurityScreen(SecurityMode.SimPin)
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
-            .thenReturn(SecurityMode.SimPin)
-        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
-
-        underTest.showNextSecurityScreenOrFinish(
-            /* authenticated= */ true,
-            TARGET_USER_ID,
-            /* bypassSecondaryLockScreen= */ true,
-            SecurityMode.SimPin
-        )
-
-        // THEN the next security method of None will dismiss keyguard.
-        verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
-    }
-
-    @Test
     fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
         val registeredSwipeListener = registeredSwipeListener
         whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
@@ -753,7 +744,7 @@
     }
 
     @Test
-    fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+    fun dismissesKeyguard_whenSceneChangesToGone() =
         sceneTestUtils.testScope.runTest {
             featureFlags.set(Flags.SCENE_CONTAINER, true)
 
@@ -815,23 +806,30 @@
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
-            // While not listening, moving back to the bouncer does not dismiss the keyguard.
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            // While not listening, moving to the lockscreen does not dismiss the keyguard.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
             sceneTransitionStateFlow.value =
-                ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+                ObservableTransitionState.Transition(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    flowOf(.5f)
+                )
             runCurrent()
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
-            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
             // Reattaching the view starts listening again so moving from the bouncer scene to the
-            // gone
-            // scene now does dismiss the keyguard again.
+            // gone scene now does dismiss the keyguard again, this time from lockscreen.
             underTest.onViewAttached()
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
             sceneTransitionStateFlow.value =
-                ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Gone,
+                    flowOf(.5f)
+                )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index f8262d4..210f3cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -21,9 +21,9 @@
 
     private lateinit var keyguardStatusView: KeyguardStatusView
     private val mediaView: View
-        get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_media_container)
     private val statusViewContainer: ViewGroup
-        get() = keyguardStatusView.findViewById(R.id.status_view_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_container)
     private val childrenExcludingMedia
         get() = statusViewContainer.children.filter { it != mediaView }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
                 mVibrator,
                 mAuthRippleController,
                 mResources,
-                new KeyguardTransitionInteractor(mTransitionRepository,
-                        TestScopeProvider.getTestScope().getBackgroundScope()),
+                KeyguardTransitionInteractorFactory.create(
+                        TestScopeProvider.getTestScope().getBackgroundScope(),
+                                mTransitionRepository).getKeyguardTransitionInteractor(),
                 KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
 
     @Test
     fun testOnLayout() {
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background).cornerRadius = 50f
         reset(background)
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background, never()).cornerRadius = anyFloat()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 6fe8892..9f9b9a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -19,9 +19,11 @@
 import android.testing.AndroidTestingRunner
 import android.transition.TransitionValues
 import android.view.View
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -84,5 +86,5 @@
     startValues: TransitionValues?,
     endValues: TransitionValues?
 ): Animator? {
-    return createAnimator(/* sceneRoot= */ null, startValues, endValues)
+    return createAnimator(/* sceneRoot= */ mock(), startValues, endValues)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a39..ea7cc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.decor.FaceScanningProviderFactory
 import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@
             R.bool.config_fillMainBuiltInDisplayCutout,
             true
         )
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
         underTest =
             FaceScanningProviderFactory(
                 authController,
@@ -87,7 +91,8 @@
                 statusBarStateController,
                 keyguardUpdateMonitor,
                 mock(Executor::class.java),
-                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+                featureFlags,
             )
 
         whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
deleted file mode 100644
index b47b08c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ /dev/null
@@ -1,487 +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;
-
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.fail;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ForegroundServiceControllerTest extends SysuiTestCase {
-    private ForegroundServiceController mFsc;
-    private ForegroundServiceNotificationListener mListener;
-    private NotifCollectionListener mCollectionListener;
-    @Mock private AppOpsController mAppOpsController;
-    @Mock private Handler mMainHandler;
-    @Mock private NotifPipeline mNotifPipeline;
-
-    @Before
-    public void setUp() throws Exception {
-        // allow the TestLooper to be asserted as the main thread these tests
-        allowTestableLooperAsMainThread();
-
-        MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
-        mListener = new ForegroundServiceNotificationListener(
-                mContext, mFsc, mNotifPipeline);
-        mListener.init();
-        ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        verify(mNotifPipeline).addCollectionListener(
-                entryListenerCaptor.capture());
-        mCollectionListener = entryListenerCaptor.getValue();
-    }
-
-    @Test
-    public void testAppOpsChangedCalledFromBgThread() {
-        try {
-            // WHEN onAppOpChanged is called from a different thread than the MainLooper
-            disallowTestableLooperAsMainThread();
-            NotificationEntry entry = createFgEntry();
-            mFsc.onAppOpChanged(
-                    AppOpsManager.OP_CAMERA,
-                    entry.getSbn().getUid(),
-                    entry.getSbn().getPackageName(),
-                    true);
-
-            // This test is run on the TestableLooper, which is not the MainLooper, so
-            // we expect an exception to be thrown
-            fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
-        } catch (IllegalStateException e) {
-            // THEN expect an exception
-        }
-    }
-
-    @Test
-    public void testAppOpsCRUD() {
-        // no crash on remove that doesn't exist
-        mFsc.onAppOpChanged(9, 1000, "pkg1", false);
-        assertNull(mFsc.getAppOps(0, "pkg1"));
-
-        // multiuser & multipackage
-        mFsc.onAppOpChanged(8, 50, "pkg1", true);
-        mFsc.onAppOpChanged(1, 60, "pkg3", true);
-        mFsc.onAppOpChanged(7, 500000, "pkg2", true);
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
-        assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
-
-        // multiple ops for the same package
-        mFsc.onAppOpChanged(9, 50, "pkg1", true);
-        mFsc.onAppOpChanged(5, 50, "pkg1", true);
-
-        assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        // remove one of the multiples
-        mFsc.onAppOpChanged(9, 50, "pkg1", false);
-        assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        // remove last op
-        mFsc.onAppOpChanged(1, 60, "pkg3", false);
-        assertNull(mFsc.getAppOps(0, "pkg3"));
-    }
-
-    @Test
-    public void testDisclosurePredicate() {
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, Notification.FLAG_NO_CLEAR);
-
-        assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
-        assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
-    }
-
-    @Test
-    public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
-        final String PKG1 = "com.example.app100";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        // first add a normal notification
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        // nothing required yet
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        // now the app starts a fg service
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        // add the fg notification
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        // remove the boring notification
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
-        entryRemoved(sbn_user1_app1_fg);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-    }
-
-    @Test
-    public void testSimpleAddRemove() {
-        final String PKG1 = "com.example.app1";
-        final String PKG2 = "com.example.app2";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
-        // no services are "running"
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // switch to different package
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testDisclosureBasic() {
-        final String PKG1 = "com.example.app0";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's take out the other notification and see what happens.
-
-        entryRemoved(sbn_user1_app1);
-        assertFalse(
-                mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
-        StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
-        sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
-        entryUpdated(sbn_user1_app1_fg_sneaky,
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // ok, ok, we'll put it back
-        sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(sbn_user1_app1_fg_sneaky);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // now let's test an upgrade
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // remove it, make sure we're out of compliance again
-        entryRemoved(sbn_user1_app1); // was fg, should return true
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // importance upgrade
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        // finally, let's turn off the service
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
-        // no notifications nor app op signals that this package/userId requires system alert
-        // warning
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
-    }
-
-    @Test
-    public void testCustomLayouts_systemAlertWarningRequired() {
-        // GIVEN a notification with a custom layout
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-
-        // WHEN the custom layout entry is added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutExists_noSystemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN no system alert warning is required, since there is at least one notification
-        // with a standard layout that can display the app ops on the notification
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutRemoved_systemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added and then the standard layout notification is removed
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryRemoved(standardLayoutNotif);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
-        // GIVEN a standard layout notification and then an updated version with a customLayout
-        final String pkg = "com.example.app0";
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-        StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
-
-        // WHEN the entries is added and then updated to a custom layout
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
-            int flags) {
-        final Notification n = mock(Notification.class);
-        n.extras = new Bundle();
-        n.flags = flags;
-        return makeMockSBN(userId, pkg, id, tag, n);
-    }
-
-    private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
-            Notification n) {
-        final StatusBarNotification sbn = mock(StatusBarNotification.class);
-        when(sbn.getNotification()).thenReturn(n);
-        when(sbn.getId()).thenReturn(id);
-        when(sbn.getPackageName()).thenReturn(pkg);
-        when(sbn.getTag()).thenReturn(tag);
-        when(sbn.getUserId()).thenReturn(userid);
-        when(sbn.getUser()).thenReturn(new UserHandle(userid));
-        when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn =
-                makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
-        return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-    }
-
-    private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
-        final Notification n = mock(Notification.class);
-        n.flags = Notification.FLAG_ONGOING_EVENT;
-        final Bundle extras = new Bundle();
-        if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
-        n.extras = extras;
-        n.when = System.currentTimeMillis() - 10000; // ten seconds ago
-        final StatusBarNotification sbn = makeMockSBN(userid, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, n);
-        sbn.getNotification().extras = extras;
-        return sbn;
-    }
-
-    private NotificationEntry addFgEntry() {
-        NotificationEntry entry = createFgEntry();
-        mCollectionListener.onEntryAdded(entry);
-        return entry;
-    }
-
-    private NotificationEntry createFgEntry() {
-        return new NotificationEntryBuilder()
-                .setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
-                .setImportance(NotificationManager.IMPORTANCE_DEFAULT)
-                .build();
-    }
-
-    private void entryRemoved(StatusBarNotification notification) {
-        mCollectionListener.onEntryRemoved(
-                new NotificationEntryBuilder()
-                        .setSbn(notification)
-                        .build(),
-                REASON_APP_CANCEL);
-    }
-
-    private void entryAdded(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryAdded(entry);
-    }
-
-    private void entryUpdated(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryUpdated(entry);
-    }
-
-    @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
-    @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
-    private static final String TEST_PACKAGE_NAME = "test";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 796e665..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -92,6 +92,8 @@
 import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -226,13 +228,16 @@
         doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
         doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
 
+        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+        featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
         mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
                 BOUNDS_POSITION_TOP,
                 mAuthController,
                 mStatusBarStateController,
                 mKeyguardUpdateMonitor,
                 mExecutor,
-                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+                featureFlags));
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
                 mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
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 a48fa5d..0fb0b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -721,6 +721,36 @@
     }
 
     @Test
+    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        View closeButton = getInternalView(R.id.close_button);
+        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+        View topRightCorner = getInternalView(R.id.top_right_corner);
+        View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+        assertEquals(View.VISIBLE, closeButton.getVisibility());
+        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+
+        assertEquals(View.GONE, closeButton.getVisibility());
+        assertEquals(View.GONE, bottomRightCorner.getVisibility());
+        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+        assertEquals(View.GONE, topRightCorner.getVisibility());
+        assertEquals(View.GONE, topLeftCorner.getVisibility());
+    }
+
+    @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 316de59..2233e322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -70,7 +70,7 @@
         assertTrue(dialog.isShowing)
 
         // The dialog is now fullscreen.
-        val window = dialog.window
+        val window = checkNotNull(dialog.window)
         val decorView = window.decorView as DecorView
         assertEquals(MATCH_PARENT, window.attributes.width)
         assertEquals(MATCH_PARENT, window.attributes.height)
@@ -172,14 +172,15 @@
         // Important: the power menu animation relies on this behavior to know when to animate (see
         // http://ag/16774605).
         val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
-        dialog.window.setWindowAnimations(0)
-        assertEquals(0, dialog.window.attributes.windowAnimations)
+        val window = checkNotNull(dialog.window)
+        window.setWindowAnimations(0)
+        assertEquals(0, window.attributes.windowAnimations)
 
         val touchSurface = createTouchSurface()
         runOnMainThreadAndWaitForIdleSync {
             dialogLaunchAnimator.showFromView(dialog, touchSurface)
         }
-        assertNotEquals(0, dialog.window.attributes.windowAnimations)
+        assertNotEquals(0, window.attributes.windowAnimations)
     }
 
     @Test
@@ -351,13 +352,14 @@
 
         init {
             // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
-            window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            checkNotNull(window).setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
         }
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(contentView)
 
+            val window = checkNotNull(window)
             window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
             window.setBackgroundDrawable(windowBackground)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 61a6512..b23f7f2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -19,6 +19,8 @@
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 
+import static com.android.systemui.appops.AppOpsControllerImpl.OPS_MIC;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -171,6 +173,28 @@
                 TEST_UID, TEST_PACKAGE_NAME, true);
     }
 
+
+    // Only the app ops in the {@link com.android.systemui.appops.AppOpsControllerImpl.OPS} will be
+    // supported by the {@link AppOpsControllerImpl} to add callbacks. The state changes of active
+    // app ops will be notified by the callback.
+    @Test
+    public void addCallback_partialIncludedCode() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mController.onOpActiveChanged(
+                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID, TEST_PACKAGE_NAME,
+                true);
+        assertEquals(2, mController.getActiveAppOps().size());
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                TEST_UID, TEST_PACKAGE_NAME, true);
+        verify(mCallback, never()).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
     @Test
     public void addCallback_notIncludedCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
@@ -504,41 +528,17 @@
     }
 
     @Test
-    public void testUnpausedRecordingSentActive() {
-        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
-        mTestableLooper.processAllMessages();
-        mController.onOpActiveChanged(
-                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
-
-        mTestableLooper.processAllMessages();
-        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
-
-        mTestableLooper.processAllMessages();
-
-        verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    public void testUnPausedRecordingSentActive() {
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            verifyUnPausedSentActive(OPS_MIC[i]);
+        }
     }
 
     @Test
     public void testAudioPausedSentInactive() {
-        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
-        mTestableLooper.processAllMessages();
-        mController.onOpActiveChanged(
-                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
-        mTestableLooper.processAllMessages();
-
-        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
-        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
-        when(mockARC.isClientSilenced()).thenReturn(true);
-
-        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
-        mTestableLooper.processAllMessages();
-
-        InOrder inOrder = inOrder(mCallback);
-        inOrder.verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
-        inOrder.verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            verifyAudioPausedSentInactive(OPS_MIC[i]);
+        }
     }
 
     @Test
@@ -673,6 +673,41 @@
         assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
     }
 
+    private void verifyUnPausedSentActive(int micOpCode) {
+        mController.addCallback(new int[]{micOpCode}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID,
+                TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(micOpCode, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    private void verifyAudioPausedSentInactive(int micOpCode) {
+        mController.addCallback(new int[]{micOpCode}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID_OTHER,
+                TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
+        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
+        when(mockARC.isClientSilenced()).thenReturn(true);
+
+        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
+        mTestableLooper.processAllMessages();
+
+        InOrder inOrder = inOrder(mCallback);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e3e6130..4e52e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -139,6 +139,7 @@
     @Before
     fun setup() {
         featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+        featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @After
@@ -151,7 +152,10 @@
     @Test
     fun testNotifiesAnimatedIn() {
         initializeFingerprintContainer()
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -196,7 +200,10 @@
         waitForIdleSync()
 
         // attaching the view resets the state and allows this to happen again
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -211,7 +218,10 @@
 
         // the first time is triggered by initializeFingerprintContainer()
         // the second time was triggered by dismissWithoutCallback()
-        verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback, times(2)).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -517,10 +527,11 @@
         { authBiometricFingerprintViewModel },
         { promptSelectorInteractor },
         { bpCredentialInteractor },
-        PromptViewModel(promptSelectorInteractor, vibrator),
+        PromptViewModel(promptSelectorInteractor, vibrator, featureFlags),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
-        fakeExecutor
+        fakeExecutor,
+        vibrator
     ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
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 3d4171f..48e5131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -197,6 +197,8 @@
     private ArgumentCaptor<String> mMessageCaptor;
     @Mock
     private Resources mResources;
+    @Mock
+    private VibratorHelper mVibratorHelper;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -515,7 +517,7 @@
 
         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
         assertThat(mMessageCaptor.getValue()).isEqualTo(
-                mContext.getString(R.string.biometric_face_not_recognized));
+                mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
     }
 
     @Test
@@ -1097,7 +1099,7 @@
                     () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel,
                     mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mUdfpsUtils);
+                    mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 2b08c66..994db46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -164,7 +164,7 @@
         context.addMockSystemService(WindowManager::class.java, windowManager)
 
         whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
-        whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+        whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
             .thenReturn(mock(LottieAnimationView::class.java))
         with(mock(ViewPropertyAnimator::class.java)) {
             whenEver(sideFpsView.animate()).thenReturn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 40b1f20..4d19543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.HapticFeedbackConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -33,6 +34,8 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
@@ -71,13 +74,15 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel = PromptViewModel(selector, vibrator)
+        viewModel = PromptViewModel(selector, vibrator, featureFlags)
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -149,6 +154,29 @@
             verify(vibrator, never()).vibrateAuthError(any())
         }
 
+    @Test
+    fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+        viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+        if (expectConfirmation) {
+            viewModel.confirmAuthenticated()
+        }
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+    }
+
+    @Test
+    fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        viewModel.showTemporaryError("test", "messageAfterError", false)
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -499,6 +527,7 @@
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
         val legacyState by collectLastValue(viewModel.legacyState)
+        val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
 
         if (testCase.isCoex && testCase.authenticatedByFingerprint) {
             viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -507,7 +536,11 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+        if (confirmationRequired == true) {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+        } else {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        }
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
         assertThat(authenticating).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d6cafcb..5a5c058 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -211,7 +211,7 @@
                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
         val expectedCenterX: Float
         val expectedCenterY: Float
-        when (context.display.rotation) {
+        when (checkNotNull(context.display).rotation) {
             Surface.ROTATION_90 -> {
                 expectedCenterX = width * normalizedPortPosY
                 expectedCenterY = height * (1 - normalizedPortPosX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index 42f28c8..2ae342a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -125,7 +125,7 @@
             control
         )
         cvh.bindData(cws, false)
-        val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon)
+        val chevronIcon = baseLayout.requireViewById<View>(R.id.chevron_icon)
 
         assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE)
     }
@@ -138,4 +138,4 @@
 private val DRAWABLE = GradientDrawable()
 private val COLOR = ColorStateList.valueOf(0xffff00)
 private val DEFAULT_CONTROL = Control.StatelessBuilder(
-        CONTROL_ID, mock(PendingIntent::class.java)).build()
\ No newline at end of file
+        CONTROL_ID, mock(PendingIntent::class.java)).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index fcd6568..a400ff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -365,7 +365,8 @@
         val selectedItems =
             listOf(
                 SelectedItem.StructureItem(
-                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                    StructureInfo(checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")),
+                        "a", ArrayList())
                 ),
             )
         preferredPanelRepository.setSelectedComponent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 49cdfa7..7311f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -33,15 +33,14 @@
 import android.app.AlarmManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.After;
@@ -53,6 +52,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@TestableLooper.RunWithLooper
 public class DozeUiTest extends SysuiTestCase {
 
     @Mock
@@ -62,23 +62,19 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
     private DozeHost mHost;
     @Mock
     private DozeLog mDozeLog;
-    @Mock
-    private TunerService mTunerService;
     private WakeLockFake mWakeLock;
     private Handler mHandler;
     private HandlerThread mHandlerThread;
     private DozeUi mDozeUi;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
+        DejankUtils.setImmediate(true);
 
         mHandlerThread = new HandlerThread("DozeUiTest");
         mHandlerThread.start();
@@ -86,12 +82,13 @@
         mHandler = mHandlerThread.getThreadHandler();
 
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
-                mDozeParameters, mStatusBarStateController, mDozeLog);
+                mDozeParameters, mDozeLog);
         mDozeUi.setDozeMachine(mMachine);
     }
 
     @After
     public void tearDown() throws Exception {
+        DejankUtils.setImmediate(false);
         mHandlerThread.quit();
         mHandler = null;
         mHandlerThread = null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 847d58b..4a79a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -236,7 +236,7 @@
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
-
+        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
 
         DejankUtils.setImmediate(true);
 
@@ -1068,7 +1068,8 @@
                 mSystemClock,
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
-                mSystemPropertiesHelper);
+                mSystemPropertiesHelper,
+                () -> mock(WindowManagerLockscreenVisibilityManager.class));
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 85ee0e4..fe5b812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(FACE_AUTH_REFACTOR, true)
+                set(KEYGUARD_WM_STATE_REFACTOR, false)
+            }
         val withDeps =
             KeyguardInteractorFactory.create(
                 featureFlags = featureFlags,
@@ -332,9 +337,6 @@
                 )
                 .isFalse()
 
-            whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
-            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
-
             whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
             assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
 
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 5e3376a..5ead16b 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
@@ -63,6 +63,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -193,7 +194,7 @@
             assertThat(underTest.isKeyguardShowing()).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isShowing).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isOccluded).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
             assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
             captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardSurfaceBehindRepositoryImpl()
+    }
+
+    @Test
+    fun testSetAnimatingSurface() {
+        testScope.runTest {
+            val values by collectValues(underTest.isAnimatingSurface)
+
+            runCurrent()
+            underTest.setAnimatingSurface(true)
+            runCurrent()
+            underTest.setAnimatingSurface(false)
+            runCurrent()
+
+            // Default (first) value should be false.
+            assertThat(values).isEqualTo(listOf(false, true, false))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromLockscreenTransitionInteractor
+
+    // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+    // interactor is provided to any classes that need it.
+    override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromLockscreenTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                shadeRepository = FakeShadeRepository(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+            runCurrent()
+
+            assertEquals(
+                values,
+                listOf(
+                    null, // We should start null ("don't care").
+                )
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+    // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+    // underTest interactor is provided to any classes that need it.
+    override var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromPrimaryBouncerTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                keyguardSecurityModel = mock(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false, // Surface is only made visible once the bouncer UI animates out.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false,
+                    true, // Surface should eventually be visible.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardSurfaceBehindInteractor
+    private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+    private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+    private val testScope = TestScope()
+
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
+
+        repository = FakeKeyguardSurfaceBehindRepository()
+        underTest =
+            KeyguardSurfaceBehindInteractor(
+                repository = repository,
+                fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+                transitionInteractor = transitionInteractor,
+            )
+    }
+
+    @Test
+    fun viewParamsSwitchToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+
+            // Start on the LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            // We're on LOCKSCREEN; we should be using the default params.
+            assertEquals(1, values.size)
+            assertTrue(values[0].alpha == 0f)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+            // surface behind model.
+            assertEquals(2, values.size)
+            assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+            // surface behind model.
+            assertEquals(3, values.size)
+            assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+            // alpha=1f when we're GONE.
+            assertEquals(4, values.size)
+            assertEquals(1f, values[3].alpha)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    lateinit var keyguardRepository: FakeKeyguardRepository
+    lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    lateinit var keyguardInteractor: KeyguardInteractor
+    lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    /**
+     * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+     * instance of the interactor for the test.
+     */
+    open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        null
+    open var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        null
+
+    open fun setUp() {
+        keyguardRepository = FakeKeyguardRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        keyguardInteractor =
+            KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                    scope = testScope.backgroundScope,
+                    fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+                            ?: Lazy { mock() },
+                    fromPrimaryBouncerTransitionInteractor =
+                        fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+                )
+                .also {
+                    fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+                    fromPrimaryBouncerTransitionInteractorLazy =
+                        it.fromPrimaryBouncerTransitionInteractor
+                }
+                .keyguardTransitionInteractor
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index aa6bd4e..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -104,12 +104,21 @@
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+            }
 
         transitionInteractor =
             KeyguardTransitionInteractorFactory.create(
                     scope = testScope,
                     repository = transitionRepository,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+                    fromPrimaryBouncerTransitionInteractor = {
+                        fromPrimaryBouncerTransitionInteractor
+                    },
                 )
                 .keyguardTransitionInteractor
 
@@ -119,6 +128,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     shadeRepository = shadeRepository,
                 )
                 .apply { start() }
@@ -129,6 +139,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                 )
                 .apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index baa5ee8..1dcb55d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.keyguard.util.IndicationHelper
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,7 @@
     private lateinit var configurationRepository: FakeConfigurationRepository
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var powerRepository: FakePowerRepository
 
     @Mock private lateinit var indicationHelper: IndicationHelper
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -102,6 +105,7 @@
                 set(Flags.DELAY_BOUNCER, false)
             }
         trustRepository = FakeTrustRepository()
+        powerRepository = FakePowerRepository()
         underTest =
             OccludingAppDeviceEntryInteractor(
                 BiometricMessageInteractor(
@@ -145,6 +149,14 @@
                 testScope.backgroundScope,
                 mockedContext,
                 activityStarter,
+                PowerInteractor(
+                    powerRepository,
+                    keyguardRepository,
+                    falsingCollector = mock(),
+                    screenOffAnimationController = mock(),
+                    statusBarStateController = mock(),
+                ),
+                FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
             )
     }
 
@@ -160,6 +172,18 @@
         }
 
     @Test
+    fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            powerRepository.setInteractive(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
     fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
         testScope.runTest {
             givenOnOccludingApp(false)
@@ -291,6 +315,7 @@
         }
 
     private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+        powerRepository.setInteractive(true)
         keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
         keyguardRepository.setKeyguardShowing(isOnOccludingApp)
         bouncerRepository.setPrimaryShow(!isOnOccludingApp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+    @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+    private val testScope = TestScope()
+
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(lockscreenSurfaceVisibilityFlow)
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+        whenever(surfaceBehindInteractor.isAnimatingSurface)
+            .thenReturn(surfaceBehindIsAnimatingFlow)
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .also { keyguardInteractor = it.keyguardInteractor }
+                .keyguardTransitionInteractor
+
+        underTest =
+            WindowManagerLockscreenVisibilityInteractor(
+                keyguardInteractor = keyguardInteractor,
+                transitionInteractor = transitionInteractor,
+                surfaceBehindInteractor = surfaceBehindInteractor,
+                fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor,
+            )
+    }
+
+    @Test
+    fun surfaceBehindVisibility_switchesToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // We should start with the surface invisible on LOCKSCREEN.
+                ),
+                values
+            )
+
+            val lockscreenSpecificSurfaceVisibility = true
+            lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+            // lockscreenSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                ),
+                values
+            )
+
+            // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false, // FINISHED (LOCKSCREEN)
+                ),
+                values
+            )
+
+            val bouncerSpecificVisibility = true
+            primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+            // the
+            // primaryBouncerSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false,
+                    bouncerSpecificVisibility,
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // Still true when we're FINISHED -> GONE, since we're still animating.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // False once the animation ends.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // We're happily animating while transitioning to gone.
+                ),
+                values
+            )
+
+            // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // Despite the animator still running, this should be false.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // The animator ending should have no effect.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun lockscreenVisibility_visibleWhenGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    // LOCKSCREEN.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false, // Once we're fully GONE, the lockscreen should not be visible.
+                ),
+                values
+            )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+    @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+    @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+    private var isAnimatingSurface: Boolean? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        underTest =
+            KeyguardSurfaceBehindParamsApplier(
+                executor = executor,
+                keyguardViewController = keyguardViewController,
+                interactor = interactor,
+            )
+
+        doAnswer {
+                (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+            }
+            .whenever(interactor)
+            .setAnimatingSurface(anyBoolean())
+    }
+
+    @After
+    fun tearDown() {
+        animatorTestRule.advanceTimeBy(1000.toLong())
+    }
+
+    @Test
+    fun testNotAnimating_setParamsWithNoAnimation() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                alpha = 0.3f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now explicitly not be animating the surface.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_paramsThenSurfaceProvided() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_surfaceThenParamsProvided() {
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // The default params (which do not animate) should have been applied, so we're explicitly
+        // NOT animating yet.
+        assertFalse(checkNotNull(isAnimatingSurface))
+
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_thenReleased_animatingIsFalse() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        assertTrue(checkNotNull(isAnimatingSurface))
+
+        underTest.notifySurfaceReleased()
+
+        // Releasing the surface should immediately cancel animators.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+    private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+
+        underTest =
+            WindowManagerLockscreenVisibilityManager(
+                executor = executor,
+                activityTaskManagerService = activityTaskManagerService,
+                keyguardStateController = keyguardStateController,
+                keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+            )
+    }
+
+    @Test
+    fun testLockscreenVisible_andAodVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+        underTest.setLockscreenShown(false)
+        underTest.setAodVisible(false)
+
+        verify(activityTaskManagerService).setLockScreenShown(false, false)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
         bouncerRepository = FakeKeyguardBouncerRepository()
         transitionRepository = FakeKeyguardTransitionRepository()
         shadeRepository = FakeShadeRepository()
-        val transitionInteractor =
-            KeyguardTransitionInteractor(
-                transitionRepository,
-                testScope.backgroundScope,
-            )
         val keyguardInteractor =
             KeyguardInteractorFactory.create(
+                    repository = keyguardRepository,
                     featureFlags = featureFlags,
                 )
                 .keyguardInteractor
+
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             FingerprintViewModel(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
                 bouncerRepository = it.bouncerRepository
             }
 
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             UdfpsLockscreenViewModel(
                 context,
                 lockscreenColorResId,
                 alternateBouncerResId,
-                KeyguardTransitionInteractor(
-                    transitionRepository,
-                    testScope.backgroundScope,
-                ),
+                transitionInteractor,
                 UdfpsKeyguardInteractor(
                     configRepository,
                     BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5b8272b0..ef51e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -132,7 +133,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
         transitionRepository = FakeKeyguardTransitionRepository()
         mediaCarouselController =
             MediaCarouselController(
@@ -152,7 +153,11 @@
                 debugLogger,
                 mediaFlags,
                 keyguardUpdateMonitor,
-                KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScopeProvider.getTestScope().backgroundScope,
+                        repository = transitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalSettings
             )
         verify(configurationController).addCallback(capture(configListener))
@@ -730,13 +735,13 @@
 
     @Test
     fun testOnLocaleListChanged_playersAreAddedBack() {
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator, never()).tintList =
             ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
 
-        context.resources.configuration.locales = LocaleList(Locale.UK, Locale.US, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.UK, Locale.US, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator).tintList =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index ee3b80a..906420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -123,7 +123,7 @@
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
         val bounds = Rect(0, 0, width, height)
-        val windowMetrics = WindowMetrics(bounds, null)
+        val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 3a74c72..7bd97ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -108,20 +108,6 @@
         }
 
     @Test
-    fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
-        testScope.runTest {
-            val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
-
-            fakeMediaProjectionManager.dispatchOnSessionSet(
-                session =
-                    ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
-            )
-
-            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
-        }
-
-    @Test
     fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index fab1de0..2d3dc58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -73,7 +73,7 @@
                 context,
                 windowManager,
                 ViewConfiguration.get(context),
-                Handler.createAsync(Looper.myLooper()),
+                Handler.createAsync(checkNotNull(Looper.myLooper())),
                 vibratorHelper,
                 configurationController,
                 latencyTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index d933b57..1536c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -69,6 +69,7 @@
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlin.test.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -672,7 +673,7 @@
             extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
         }
         iconCaptor.value?.let { icon ->
-            assertThat(icon).isNotNull()
+            assertNotNull(icon)
             assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
         }
     }
@@ -755,7 +756,7 @@
             assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
             assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
             assertThat(isLongLived).isEqualTo(true)
-            assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+            assertThat(icon?.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
             assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
                 .isEqualTo(NOTE_TASK_PACKAGE_NAME)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
index db96d55..14ecf93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
@@ -143,6 +143,16 @@
     }
 
     @Test
+    fun testVoiceActivationPrivacyItems() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID,
+                TEST_PACKAGE_NAME, 0)))
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
+        val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
+        assertEquals(1, privacyItems.size)
+        assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
+    }
+
+    @Test
     fun testSimilarItemsDifferentTimeStamp() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 1)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index fda63ed..72c31b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -372,12 +373,23 @@
             assertThat(loadTilesForUser(0)).isNull()
         }
 
+    @Test
+    fun emptyTilesReplacedByDefaultInSettings() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+            runCurrent()
+
+            assertThat(loadTilesForUser(0))
+                .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
+        }
+
     private fun getDefaultTileSpecs(): List<TileSpec> {
         return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
     }
 
-    private fun storeTilesForUser(specs: String, forUser: Int) {
+    private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
+        runCurrent()
     }
 
     private fun loadTilesForUser(forUser: Int): String? {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 30cea2d..6689514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -648,6 +648,22 @@
             assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
         }
 
+    @Test
+    fun tileAddedOnEmptyList_blocked() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+            val newTile = TileSpec.create("c")
+
+            underTest.addTile(newTile)
+
+            assertThat(tiles!!.isEmpty()).isTrue()
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles!!.size).isEqualTo(3)
+        }
+
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
         this.state = state
         this.label = label
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
new file mode 100644
index 0000000..53c04cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Integration test cases for the Scene Framework.
+ *
+ * **Principles**
+ * * All test cases here should be done from the perspective of the view-models of the system.
+ * * Focus on happy paths, let smaller unit tests focus on failure cases.
+ * * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
+ *   Therefore, when adding or modifying test cases, consider whether what you're testing is better
+ *   covered by a more granular unit test.
+ * * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
+ *   [emulateUserDrivenTransition]).
+ * * All tests start with the device locked and with a PIN auth method. The class offers useful
+ *   methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
+ *   state that makes more sense for your test case.
+ * * All helper methods in this class make assertions that are meant to make sure that they're only
+ *   being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
+ *   put to sleep a device that's already asleep, etc.).
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneFrameworkIntegrationTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+
+    private val sceneContainerConfig = utils.fakeSceneContainerConfig()
+    private val sceneRepository =
+        utils.fakeSceneContainerRepository(
+            containerConfig = sceneContainerConfig,
+        )
+    private val sceneInteractor =
+        utils.sceneInteractor(
+            repository = sceneRepository,
+        )
+
+    private val authenticationRepository = utils.authenticationRepository()
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+            sceneInteractor = sceneInteractor,
+        )
+
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
+        )
+    private val sceneContainerViewModel =
+        SceneContainerViewModel(
+                interactor = sceneInteractor,
+            )
+            .apply { setTransitionState(transitionState) }
+
+    private val bouncerInteractor =
+        utils.bouncerInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
+    private val bouncerViewModel =
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+        )
+
+    private val lockscreenSceneViewModel =
+        LockscreenSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val shadeSceneViewModel =
+        ShadeSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val keyguardRepository = utils.keyguardRepository()
+    private val keyguardInteractor =
+        utils.keyguardInteractor(
+            repository = keyguardRepository,
+        )
+
+    @Before
+    fun setUp() {
+        val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
+
+        authenticationRepository.setUnlocked(false)
+
+        val displayTracker = FakeDisplayTracker(context)
+        val sysUiState = SysUiState(displayTracker)
+        val startable =
+            SceneContainerStartable(
+                applicationScope = testScope.backgroundScope,
+                sceneInteractor = sceneInteractor,
+                authenticationInteractor = authenticationInteractor,
+                keyguardInteractor = keyguardInteractor,
+                featureFlags = featureFlags,
+                sysUiState = sysUiState,
+                displayId = displayTracker.defaultDisplayId,
+                sceneLogger = mock(),
+            )
+        startable.start()
+
+        assertWithMessage("Initial scene key mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(sceneContainerConfig.initialSceneKey)
+        assertWithMessage("Initial scene container visibility mismatch!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isTrue()
+    }
+
+    @Test
+    fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            lockscreenSceneViewModel.onLockButtonClicked()
+            assertCurrentScene(SceneKey.Bouncer)
+            emulateUiSceneTransition()
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
+        testScope.runTest {
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to dismiss the lockscreen.
+            emulateUserDrivenTransition(to = SceneKey.Gone)
+            assertCurrentScene(SceneKey.Gone)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.None)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.Swipe)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_switchesToLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_wakeUp_unlock() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Pretend like the timeout elapsed and now lock the device.
+            lockDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    /**
+     * Asserts that the current scene in the view-model matches what's expected.
+     *
+     * Note that this doesn't assert what the current scene is in the UI.
+     */
+    private fun TestScope.assertCurrentScene(expected: SceneKey) {
+        runCurrent()
+        assertWithMessage("Current scene mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(expected)
+    }
+
+    /**
+     * Returns the [SceneKey] of the current scene as displayed in the UI.
+     *
+     * This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
+     * the UI must gradually transition between scenes.
+     */
+    private fun getCurrentSceneInUi(): SceneKey {
+        return when (val state = transitionState.value) {
+            is ObservableTransitionState.Idle -> state.scene
+            is ObservableTransitionState.Transition -> state.fromScene
+        }
+    }
+
+    /** Updates the current authentication method and related states in the data layer. */
+    private fun TestScope.setAuthMethod(
+        authMethod: DomainLayerAuthenticationMethodModel,
+    ) {
+        // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
+        // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
+        // is not an observable that can trigger a new evaluation.
+        authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
+        authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+        if (!authMethod.isSecure) {
+            // When the auth method is not secure, the device is never considered locked.
+            authenticationRepository.setUnlocked(true)
+        }
+        runCurrent()
+    }
+
+    /**
+     * Emulates a complete transition in the UI from whatever the current scene is in the UI to
+     * whatever the current scene should be, based on the value in
+     * [SceneContainerViewModel.onSceneChanged].
+     *
+     * This should post a series of values into [transitionState] to emulate a gradual scene
+     * transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
+     *
+     * The method asserts that a transition is actually required. E.g. it will fail if the current
+     * scene in [transitionState] is already caught up with the scene in
+     * [SceneContainerViewModel.currentScene].
+     *
+     * @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
+     *   of the UI transition.
+     */
+    private fun TestScope.emulateUiSceneTransition(
+        expectedVisible: Boolean = true,
+    ) {
+        val to = sceneContainerViewModel.currentScene.value
+        val from = getCurrentSceneInUi()
+        assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
+            .that(to.key)
+            .isNotEqualTo(from)
+
+        // Begin to transition.
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = getCurrentSceneInUi(),
+                toScene = to.key,
+                progress = progressFlow,
+            )
+        runCurrent()
+
+        // Report progress of transition.
+        while (progressFlow.value < 1f) {
+            progressFlow.value += 0.2f
+            runCurrent()
+        }
+
+        // End the transition and report the change.
+        transitionState.value = ObservableTransitionState.Idle(to.key)
+
+        sceneContainerViewModel.onSceneChanged(to)
+        runCurrent()
+
+        assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isEqualTo(expectedVisible)
+    }
+
+    /**
+     * Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
+     * causes a scene change to the [to] scene.
+     *
+     * This also includes the emulation of the resulting UI transition that culminates with the UI
+     * catching up with the requested scene change (see [emulateUiSceneTransition]).
+     *
+     * @param to The scene to transition to.
+     */
+    private fun TestScope.emulateUserDrivenTransition(
+        to: SceneKey?,
+    ) {
+        checkNotNull(to)
+
+        sceneInteractor.changeScene(SceneModel(to), "reason")
+        assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
+
+        emulateUiSceneTransition(
+            expectedVisible = to != SceneKey.Gone,
+        )
+    }
+
+    /**
+     * Locks the device immediately (without delay).
+     *
+     * Asserts the device to be lockable (e.g. that the current authentication is secure).
+     *
+     * Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
+     */
+    private suspend fun TestScope.lockDevice() {
+        val authMethod = authenticationInteractor.getAuthenticationMethod()
+        assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
+            .that(authMethod.isSecure)
+            .isTrue()
+
+        authenticationRepository.setUnlocked(false)
+        runCurrent()
+    }
+
+    /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
+    private fun TestScope.unlockDevice() {
+        assertWithMessage("Cannot unlock a device that's already unlocked!")
+            .that(authenticationInteractor.isUnlocked.value)
+            .isFalse()
+
+        lockscreenSceneViewModel.onLockButtonClicked()
+        runCurrent()
+        emulateUiSceneTransition()
+
+        enterPin()
+        emulateUiSceneTransition(
+            expectedVisible = false,
+        )
+    }
+
+    /**
+     * Enters the correct PIN in the bouncer UI.
+     *
+     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * before proceeding.
+     *
+     * Does not assert that the device is locked or unlocked.
+     */
+    private fun TestScope.enterPin() {
+        assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+            .that(getCurrentSceneInUi())
+            .isEqualTo(SceneKey.Bouncer)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+        assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+            .that(authMethodViewModel)
+            .isInstanceOf(PinBouncerViewModel::class.java)
+
+        val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+        FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+            pinBouncerViewModel.onPinButtonClicked(digit)
+        }
+        pinBouncerViewModel.onAuthenticateButtonClicked()
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
+    private fun TestScope.wakeUpDevice() {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot wake up device as it's already awake!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isFalse()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.AWAKE)
+        )
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
+    private suspend fun TestScope.putDeviceToSleep(
+        instantlyLockDevice: Boolean = true,
+    ) {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot put device to sleep as it's already asleep!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isTrue()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
+        )
+        runCurrent()
+
+        if (instantlyLockDevice) {
+            lockDevice()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 0a93a7c..16cc924 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -28,9 +28,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -105,6 +102,40 @@
         }
 
     @Test
+    fun transitioningTo() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+                )
+            underTest.setTransitionState(transitionState)
+
+            val transitionTo by collectLastValue(underTest.transitioningTo)
+            assertThat(transitionTo).isNull()
+
+            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+            assertThat(transitionTo).isNull()
+
+            val progress = MutableStateFlow(0f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = underTest.desiredScene.value.key,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 0.5f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 1f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            assertThat(transitionTo).isNull()
+        }
+
+    @Test
     fun isVisible() =
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
@@ -118,81 +149,6 @@
         }
 
     @Test
-    fun finishedSceneTransitions() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
-                )
-            underTest.setTransitionState(transitionState)
-            var transitionCount = 0
-            val job = launch {
-                underTest
-                    .finishedSceneTransitions(
-                        from = SceneKey.Shade,
-                        to = SceneKey.QuickSettings,
-                    )
-                    .collect { transitionCount++ }
-            }
-
-            assertThat(transitionCount).isEqualTo(0)
-
-            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
-            transitionState.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Lockscreen,
-                    toScene = SceneKey.Shade,
-                    progress = flowOf(0.5f),
-                )
-            runCurrent()
-            underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
-            runCurrent()
-            assertThat(transitionCount).isEqualTo(0)
-
-            underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
-            transitionState.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.QuickSettings,
-                    progress = flowOf(0.5f),
-                )
-            runCurrent()
-            underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
-            runCurrent()
-            assertThat(transitionCount).isEqualTo(1)
-
-            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
-            transitionState.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.QuickSettings,
-                    toScene = SceneKey.Shade,
-                    progress = flowOf(0.5f),
-                )
-            runCurrent()
-            underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
-            runCurrent()
-            assertThat(transitionCount).isEqualTo(1)
-
-            underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
-            transitionState.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.QuickSettings,
-                    progress = flowOf(0.5f),
-                )
-            runCurrent()
-            underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
-            runCurrent()
-            assertThat(transitionCount).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
     fun remoteUserInput() =
         testScope.runTest {
             val remoteUserInput by collectLastValue(underTest.remoteUserInput)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 45db7a0..951cadd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -21,7 +21,7 @@
 import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.model.SysUiState
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -80,14 +81,13 @@
         )
 
     @Test
-    fun hydrateVisibility_featureEnabled() =
+    fun hydrateVisibility() =
         testScope.runTest {
             val currentDesiredSceneKey by
                 collectLastValue(sceneInteractor.desiredScene.map { it.key })
             val isVisible by collectLastValue(sceneInteractor.isVisible)
             val transitionStateFlow =
                 prepareState(
-                    isFeatureEnabled = true,
                     isDeviceUnlocked = true,
                     initialSceneKey = SceneKey.Gone,
                 )
@@ -123,44 +123,10 @@
         }
 
     @Test
-    fun hydrateVisibility_featureDisabled() =
-        testScope.runTest {
-            val currentDesiredSceneKey by
-                collectLastValue(sceneInteractor.desiredScene.map { it.key })
-            val isVisible by collectLastValue(sceneInteractor.isVisible)
-            val transitionStateFlow =
-                prepareState(
-                    isFeatureEnabled = false,
-                    isDeviceUnlocked = true,
-                    initialSceneKey = SceneKey.Gone,
-                )
-            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
-            assertThat(isVisible).isTrue()
-
-            underTest.start()
-
-            assertThat(isVisible).isTrue()
-
-            sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
-            transitionStateFlow.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Gone,
-                    toScene = SceneKey.Shade,
-                    progress = flowOf(0.5f),
-                )
-            assertThat(isVisible).isTrue()
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
-            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
-            assertThat(isVisible).isTrue()
-        }
-
-    @Test
-    fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
+    fun switchToLockscreenWhenDeviceLocks() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = true,
                 initialSceneKey = SceneKey.Gone,
             )
@@ -173,28 +139,10 @@
         }
 
     @Test
-    fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+    fun switchFromBouncerToGoneWhenDeviceUnlocked() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Gone,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(false)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
-        }
-
-    @Test
-    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = false,
                 initialSceneKey = SceneKey.Bouncer,
             )
@@ -207,28 +155,10 @@
         }
 
     @Test
-    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Bouncer,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(true)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
-        }
-
-    @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isBypassEnabled = true,
                 initialSceneKey = SceneKey.Lockscreen,
             )
@@ -241,11 +171,10 @@
         }
 
     @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+    fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = true,
                 isBypassEnabled = false,
                 initialSceneKey = SceneKey.Lockscreen,
             )
@@ -258,28 +187,10 @@
         }
 
     @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+    fun switchToLockscreenWhenDeviceSleepsLocked() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isBypassEnabled = true,
-                initialSceneKey = SceneKey.Lockscreen,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(true)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
-        }
-
-    @Test
-    fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = false,
                 initialSceneKey = SceneKey.Shade,
             )
@@ -292,23 +203,6 @@
         }
 
     @Test
-    fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Shade,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-            underTest.start()
-
-            keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-        }
-
-    @Test
     fun hydrateSystemUiState() =
         testScope.runTest {
             val transitionStateFlow = prepareState()
@@ -339,11 +233,10 @@
         }
 
     @Test
-    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = true,
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.None,
             )
@@ -356,11 +249,26 @@
         }
 
     @Test
-    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
+    fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = true,
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Swipe,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
             )
@@ -373,30 +281,31 @@
         }
 
     @Test
-    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
                 initialSceneKey = SceneKey.Lockscreen,
-                authenticationMethod = AuthenticationMethodModel.None,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
             )
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
+            authenticationRepository.setUnlocked(true)
+            runCurrent()
             keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
 
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
 
     private fun prepareState(
-        isFeatureEnabled: Boolean = true,
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
         initialSceneKey: SceneKey? = null,
         authenticationMethod: AuthenticationMethodModel? = null,
     ): MutableStateFlow<ObservableTransitionState> {
-        featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+        featureFlags.set(Flags.SCENE_CONTAINER, true)
         authenticationRepository.setUnlocked(isDeviceUnlocked)
         keyguardRepository.setBypassEnabled(isBypassEnabled)
         val transitionStateFlow =
@@ -410,7 +319,7 @@
             sceneInteractor.onSceneChanged(SceneModel(it), "reason")
         }
         authenticationMethod?.let {
-            authenticationRepository.setAuthenticationMethod(authenticationMethod)
+            authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
             authenticationRepository.setLockscreenEnabled(
                 authenticationMethod != AuthenticationMethodModel.None
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 07feedf..ad6909d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -126,6 +126,7 @@
 
     private fun onSpinnerItemSelected(position: Int) {
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
-        spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+        checkNotNull(spinner.onItemSelectedListener)
+            .onItemSelected(spinner, mock(), position, /* id= */ 0)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 7b3e89d..1edeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -95,7 +95,7 @@
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
-    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -150,44 +150,45 @@
         testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
-                lockscreenShadeTransitionController,
-                FalsingCollectorFake(),
-                sysuiStatusBarStateController,
-                dockManager,
-                notificationShadeDepthController,
-                view,
-                notificationPanelViewController,
-                ShadeExpansionStateManager(),
-                stackScrollLayoutController,
-                statusBarKeyguardViewManager,
-                statusBarWindowStateController,
-                lockIconViewController,
-                centralSurfaces,
-                backActionInteractor,
-                powerInteractor,
-                notificationShadeWindowController,
-                unfoldTransitionProgressProvider,
-                keyguardUnlockAnimationController,
-                notificationInsetsController,
-                ambientState,
-                pulsingGestureListener,
-                mLockscreenHostedDreamGestureListener,
-                keyguardBouncerViewModel,
-                keyguardBouncerComponentFactory,
-                mock(KeyguardMessageAreaController.Factory::class.java),
-                keyguardTransitionInteractor,
-                primaryBouncerToGoneTransitionViewModel,
-                notificationExpansionRepository,
-                featureFlags,
-                FakeSystemClock(),
-                BouncerMessageInteractor(
-                    FakeBouncerMessageRepository(),
-                    mock(BouncerMessageFactory::class.java),
-                    FakeUserRepository(),
-                    CountDownTimerUtil(),
-                    featureFlags
-                ),
-                BouncerLogger(logcatLogBuffer("BouncerLog"))
+                    lockscreenShadeTransitionController,
+                    FalsingCollectorFake(),
+                    sysuiStatusBarStateController,
+                    dockManager,
+                    notificationShadeDepthController,
+                    view,
+                    notificationPanelViewController,
+                    ShadeExpansionStateManager(),
+                    stackScrollLayoutController,
+                    statusBarKeyguardViewManager,
+                    statusBarWindowStateController,
+                    lockIconViewController,
+                    centralSurfaces,
+                    backActionInteractor,
+                    powerInteractor,
+                    notificationShadeWindowController,
+                    unfoldTransitionProgressProvider,
+                    keyguardUnlockAnimationController,
+                    notificationInsetsController,
+                    ambientState,
+                    shadeLogger,
+                    pulsingGestureListener,
+                    mLockscreenHostedDreamGestureListener,
+                    keyguardBouncerViewModel,
+                    keyguardBouncerComponentFactory,
+                    mock(KeyguardMessageAreaController.Factory::class.java),
+                    keyguardTransitionInteractor,
+                    primaryBouncerToGoneTransitionViewModel,
+                    notificationExpansionRepository,
+                    featureFlags,
+                    FakeSystemClock(),
+                    BouncerMessageInteractor(
+                        FakeBouncerMessageRepository(),
+                        mock(BouncerMessageFactory::class.java),
+                        FakeUserRepository(),
+                        CountDownTimerUtil(),
+                        featureFlags
+                    ),
+                    BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 5c3ce71..829184c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -105,6 +105,7 @@
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var ambientState: AmbientState
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -179,6 +180,7 @@
                 keyguardUnlockAnimationController,
                 notificationInsetsController,
                 ambientState,
+                shadeLogger,
                 pulsingGestureListener,
                 mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 112a09b..577b6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -584,7 +584,7 @@
     private fun emptyInsets() = mock(WindowInsets::class.java)
 
     private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
         return this
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 8d3c4b2..405199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -567,7 +567,7 @@
     private fun emptyInsets() = mock(WindowInsets::class.java)
 
     private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
         return this
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 52e0c9c..6a14a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -59,6 +60,7 @@
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var nswvc: NotificationShadeWindowViewController
     @Mock private lateinit var display: Display
+    @Mock private lateinit var touchLog: LogBuffer
 
     private lateinit var shadeController: ShadeControllerImpl
 
@@ -71,6 +73,7 @@
             ShadeControllerImpl(
                 commandQueue,
                 FakeExecutor(FakeSystemClock()),
+                touchLog,
                 keyguardStateController,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 2501f85..8f8b840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -138,19 +138,19 @@
 
     @Before
     fun setup() {
-        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever<Clock>(view.requireViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
 
-        whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+        whenever<TextView>(view.requireViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
 
-        whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+        whenever<ShadeCarrierGroup>(view.requireViewById(R.id.carrier_group)).thenReturn(carrierGroup)
 
-        whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+        whenever<BatteryMeterView>(view.requireViewById(R.id.batteryRemainingIcon))
             .thenReturn(batteryMeterView)
 
-        whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
-        whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
+        whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
 
         viewContext = Mockito.spy(context)
         whenever(view.context).thenReturn(viewContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 7443097..69b9525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -42,6 +42,7 @@
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
+            sceneInteractor = sceneInteractor,
         )
 
     private val underTest =
@@ -76,6 +77,30 @@
         }
 
     @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 58b44ae..19dc72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -236,7 +236,8 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
 
         // Given a session is created
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
@@ -258,7 +259,8 @@
 
         // Given a session is created
         val customView = Mockito.mock(TestView::class.java)
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
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 b6da20f..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,21 +17,19 @@
 
 package com.android.systemui.statusbar;
 
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
 import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.PendingIntent;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -67,17 +65,15 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
-    protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
-    protected static final int TEST_AUTO_DISMISS_TIME = 500;
+    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+    protected static final int TEST_AUTO_DISMISS_TIME = 600;
+    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
     // Number of notifications to use in tests requiring multiple notifications
     private static final int TEST_NUM_NOTIFICATIONS = 4;
-    protected static final int TEST_TIMEOUT_TIME = 15000;
+    protected static final int TEST_TIMEOUT_TIME = 2_000;
     protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
-    protected NotificationEntry mEntry;
     protected Handler mTestHandler;
-    private StatusBarNotification mSbn;
     protected boolean mTimedOut = false;
 
     @Mock protected ExpandableNotificationRow mRow;
@@ -88,8 +84,8 @@
         private TestableAlertingNotificationManager(Handler handler) {
             super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         @Override
@@ -114,7 +110,7 @@
         return new TestableAlertingNotificationManager(mTestHandler);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification n) {
+    protected StatusBarNotification createSbn(int id, Notification n) {
         return new StatusBarNotification(
                 TEST_PACKAGE_NAME /* pkg */,
                 TEST_PACKAGE_NAME,
@@ -128,45 +124,53 @@
                 0 /* postTime */);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n.build(),
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
+    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
     }
 
-    protected StatusBarNotification createNewNotification(int id) {
-        Notification.Builder n = new Notification.Builder(mContext, "")
+    protected StatusBarNotification createSbn(int id) {
+        final Notification.Builder b = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
-        return createNewSbn(id, n);
+        return createSbn(id, b);
     }
 
-    protected StatusBarNotification createStickySbn(int id) {
-        Notification stickyHun = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
-                .build();
-        stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
-        return createNewSbn(id, stickyHun);
+    protected NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected NotificationEntry createEntry(int id) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+    }
+
+    protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+            boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+        mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        TestableLooper.get(this).processMessages(2);
+
+        assertFalse("Test timed out", mTimedOut);
+        if (shouldBeAlerting) {
+            assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+        } else {
+            assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+        }
+        assertFalse("Should not still be alerting after processing",
+                anm.isAlerting(entry.getKey()));
     }
 
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
-        mSbn = createNewNotification(0 /* id */);
-        mEntry = new NotificationEntryBuilder()
-                .setSbn(mSbn)
-                .build();
-        mEntry.setRow(mRow);
+
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     @After
@@ -176,59 +180,64 @@
 
     @Test
     public void testShowNotification_addsEntry() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        alm.showNotification(mEntry);
+        alm.showNotification(entry);
 
-        assertTrue(alm.isAlerting(mEntry.getKey()));
+        assertTrue(alm.isAlerting(entry.getKey()));
         assertTrue(alm.hasNotifications());
-        assertEquals(mEntry, alm.getEntry(mEntry.getKey()));
+        assertEquals(entry, alm.getEntry(entry.getKey()));
     }
 
     @Test
     public void testShowNotification_autoDismisses() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        alm.showNotification(mEntry);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        alm.showNotification(entry);
 
-        // Wait for remove runnable and then process it immediately
-        TestableLooper.get(this).processMessages(1);
+        verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+                "auto dismiss time");
 
-        assertFalse("Test timed out", mTimedOut);
-        assertFalse(alm.isAlerting(mEntry.getKey()));
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_removeDeferred() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Try to remove but defer, since the notification has not been shown long enough.
-        alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                false /* releaseImmediately */);
 
-        assertTrue(alm.isAlerting(mEntry.getKey()));
+        assertFalse(removedImmediately);
+        assertTrue(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_forceRemove() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Remove forcibly with releaseImmediately = true.
-        alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                true /* releaseImmediately */);
 
-        assertFalse(alm.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testReleaseAllImmediately() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            StatusBarNotification sbn = createNewNotification(i);
-            NotificationEntry entry = new NotificationEntryBuilder()
-                    .setSbn(sbn)
-                    .build();
+            final NotificationEntry entry = createEntry(i);
             entry.setRow(mRow);
             alm.showNotification(entry);
         }
@@ -240,10 +249,12 @@
 
     @Test
     public void testCanRemoveImmediately_notShownLongEnough() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // The entry has just been added so we should not remove immediately.
-        assertFalse(alm.canRemoveImmediately(mEntry.getKey()));
+        assertFalse(alm.canRemoveImmediately(entry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 724ea02..e4da53a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -47,7 +47,7 @@
         processor = MediaArtworkProcessor()
 
         val point = Point()
-        context.display.getSize(point)
+        checkNotNull(context.display).getSize(point)
         screenWidth = point.x
         screenHeight = point.y
     }
@@ -106,4 +106,4 @@
         // THEN the processed bitmap is null
         assertThat(background).isNull()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2de5705..9036f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -278,7 +278,7 @@
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
 
         // WHEN a connection attempt is made and view is attached
-        val view = controller.buildAndConnectView(fakeParent)
+        val view = controller.buildAndConnectView(fakeParent)!!
         controller.stateChangeListener.onViewAttachedToWindow(view)
 
         // THEN no session is created
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index bc32759..f2207af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -24,97 +24,53 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Intent;
 import android.graphics.Color;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class AppOpsCoordinatorTest extends SysuiTestCase {
-    private static final String TEST_PKG = "test_pkg";
-    private static final int NOTIF_USER_ID = 0;
+public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
 
-    @Mock private ForegroundServiceController mForegroundServiceController;
-    @Mock private AppOpsController mAppOpsController;
+    private static final int NOTIF_USER_ID = 0;
     @Mock private NotifPipeline mNotifPipeline;
 
     private NotificationEntryBuilder mEntryBuilder;
-    private AppOpsCoordinator mAppOpsCoordinator;
-    private NotifFilter mForegroundFilter;
+    private ColorizedFgsCoordinator mColorizedFgsCoordinator;
     private NotifSectioner mFgsSection;
 
-    private FakeSystemClock mClock = new FakeSystemClock();
-    private FakeExecutor mExecutor = new FakeExecutor(mClock);
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         allowTestableLooperAsMainThread();
 
-        mAppOpsCoordinator =
-                new AppOpsCoordinator(
-                        mForegroundServiceController,
-                        mAppOpsController,
-                        mExecutor);
+        mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
 
         mEntryBuilder = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID));
 
-        mAppOpsCoordinator.attach(mNotifPipeline);
+        mColorizedFgsCoordinator.attach(mNotifPipeline);
 
-        // capture filter
-        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
-        mForegroundFilter = filterCaptor.getValue();
-
-        mFgsSection = mAppOpsCoordinator.getSectioner();
-    }
-
-    @Test
-    public void filterTest_disclosureUnnecessary() {
-        NotificationEntry entry = mEntryBuilder.build();
-        StatusBarNotification sbn = entry.getSbn();
-
-        // GIVEN the notification is a disclosure notification
-        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
-
-        // GIVEN the disclosure isn't needed for this user
-        when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
-                .thenReturn(false);
-
-        // THEN filter out the notification
-        assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
+        mFgsSection = mColorizedFgsCoordinator.getSectioner();
     }
 
     @Test
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 61da901..39b2948 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
@@ -115,6 +115,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -441,6 +442,7 @@
         mShadeController = spy(new ShadeControllerImpl(
                 mCommandQueue,
                 mMainExecutor,
+                mock(LogBuffer.class),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..56d2397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -212,13 +212,13 @@
     @Test
     fun localeListChanged_listenerNotified() {
         val config = mContext.resources.configuration
-        config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+        config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY))
         mConfigurationController.onConfigurationChanged(config)
 
         val listener = createAndAddListener()
 
         // WHEN the locales are updated
-        config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+        config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE))
         mConfigurationController.onConfigurationChanged(config)
 
         // THEN the listener is notified
@@ -274,6 +274,23 @@
     }
 
     @Test
+    fun orientationUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.orientation = Configuration.ORIENTATION_LANDSCAPE
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the orientation is updated
+        config.orientation = Configuration.ORIENTATION_PORTRAIT
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.orientationChanged).isTrue()
+    }
+
+
+    @Test
     fun multipleUpdates_listenerNotifiedOfAll() {
         val config = mContext.resources.configuration
         config.densityDpi = 14
@@ -325,6 +342,7 @@
         var themeChanged = false
         var localeListChanged = false
         var layoutDirectionChanged = false
+        var orientationChanged = false
 
         override fun onConfigChanged(newConfig: Configuration?) {
             changedConfig = newConfig
@@ -350,6 +368,9 @@
         override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
             layoutDirectionChanged = true
         }
+        override fun onOrientationChanged(orientation: Int) {
+            orientationChanged = true
+        }
 
         fun assertNoMethodsCalled() {
             assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
     private HeadsUpManagerPhone mHeadsUpManager;
     private View mOperatorNameView;
     private StatusBarStateController mStatusbarStateController;
+    private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private KeyguardBypassController mBypassController;
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
         mOperatorNameView = new View(mContext);
         mStatusbarStateController = mock(StatusBarStateController.class);
+        mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
         mBypassController = mock(KeyguardBypassController.class);
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -188,6 +191,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -283,4 +287,18 @@
                 /* delta = */ 0.001
         );
     }
+
+    @Test
+    public void onHeadsUpStateChanged_true_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+    }
+
+    @Test
+    public void onHeadsUpStateChanged_false_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+    }
 }
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 72522ca..bb20d18 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
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 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.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -62,8 +61,6 @@
 public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
-    private HeadsUpManagerPhone mHeadsUpManager;
-
     private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
             logcatLogBuffer());
     @Mock private GroupMembershipManager mGroupManager;
@@ -108,25 +105,8 @@
         }
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
-    }
-
-    @Before
-    @Override
-    public void setUp() {
-        AccessibilityManagerWrapper accessibilityMgr =
-                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
-        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
-                .thenReturn(TEST_AUTO_DISMISS_TIME);
-        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
-        mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.ambient_notification_extension_time, 500);
-
-        super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManagerPhone(
+    private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+        return new TestableHeadsUpManagerPhone(
                 mContext,
                 mHeadsUpManagerLogger,
                 mGroupManager,
@@ -141,6 +121,26 @@
         );
     }
 
+    @Override
+    protected AlertingNotificationManager createAlertingNotificationManager() {
+        return createHeadsUpManagerPhone();
+    }
+
+    @Before
+    @Override
+    public void setUp() {
+        final AccessibilityManagerWrapper accessibilityMgr =
+                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+                .thenReturn(TEST_AUTO_DISMISS_TIME);
+        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+        mDependency.injectMockDependency(NotificationShadeWindowController.class);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.ambient_notification_extension_time, 500);
+
+        super.setUp();
+    }
+
     @After
     @Override
     public void tearDown() {
@@ -149,63 +149,67 @@
 
     @Test
     public void testSnooze() {
-        mHeadsUpManager.showNotification(mEntry);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.snooze();
+        hmp.showNotification(entry);
+        hmp.snooze();
 
-        assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+        assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
     }
 
     @Test
     public void testSwipedOutNotification() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Remove should succeed because the notification is swiped out
-        mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+                /* releaseImmediately = */ false);
 
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(hmp.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testCanRemoveImmediately_swipedOut() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Notification is swiped so it can be immediately removed.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(entry.getKey()));
     }
 
     @Ignore("b/141538055")
     @Test
     public void testCanRemoveImmediately_notTopEntry() {
-        NotificationEntry laterEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build();
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+        final NotificationEntry laterEntry = createEntry(/* id = */ 1);
         laterEntry.setRow(mRow);
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.showNotification(laterEntry);
+
+        hmp.showNotification(earlierEntry);
+        hmp.showNotification(laterEntry);
 
         // Notification is "behind" a higher priority notification so we can remove it immediately.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
     }
 
     @Test
     public void testExtendHeadsUp() {
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.extendHeadsUp();
+        hmp.showNotification(entry);
+        hmp.extendHeadsUp();
 
-        // Wait for normal time runnable and extended remove runnable and process them on arrival.
-        TestableLooper.get(this).processMessages(2);
-
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Pulse was not extended", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+        verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+    // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+    private val batteryView = mock<View>()
+    private val statusIcons = mock<View>()
+    private val startIcons = mock<View>()
+    private val statusBarView =
+        mock<PhoneStatusBarView>().apply {
+            whenever(this.context).thenReturn(mContext)
+            whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+            whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+            whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+                .thenReturn(startIcons)
+        }
+    private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+    private val underTest: PhoneStatusBarTransitions by lazy {
+        PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+            // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+            // to ignore those in the tests, so clear those verifications here.
+            reset(batteryView)
+            reset(statusIcons)
+            reset(startIcons)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        context.orCreateTestableResources.addOverride(
+            R.dimen.status_bar_icon_drawing_alpha,
+            RESOURCE_ALPHA,
+        )
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_translucentMode_batteryIconShown() {
+        underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_semiTransparentMode_statusIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_transparentMode_startIconsShown() {
+        // Transparent is the default, so we need to switch to a different mode first
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    /** Regression test for b/291173113. */
+    @Test
+    fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    private fun View.capturedAlpha(): Float {
+        val captor = argumentCaptor<Float>()
+        verify(this).alpha = captor.capture()
+        return captor.value
+    }
+
+    private companion object {
+        const val RESOURCE_ALPHA = 0.34f
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 1759fb7..210c5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -463,10 +463,10 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
 
         // WHEN: get insets on the second display
         val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -482,14 +482,14 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsetsFirstCall = provider
             .getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
         provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
 
         // WHEN: get insets on the first display again
         val firstDisplayInsetsSecondCall = provider
@@ -577,4 +577,4 @@
                 " expected=$expected actual=$actual",
                 expected.equals(actual))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ed9cf3f..0da7360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -701,7 +708,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 9c52788..34c4ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -32,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
@@ -94,7 +93,6 @@
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
 
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 14edf3d..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +42,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -48,6 +49,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -63,16 +65,11 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
-    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
-    private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+    private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+    private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
 
-    private HeadsUpManager mHeadsUpManager;
-    private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
-    @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
-    @Mock private NotificationEntry mEntry;
-    @Mock private StatusBarNotification mSbn;
-    @Mock private Notification mNotification;
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
@@ -83,27 +80,81 @@
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
             super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+            mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
     }
 
+    private HeadsUpManager createHeadsUpManager() {
+        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+                mUiEventLoggerFake);
+    }
+
     @Override
     protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
+        return createHeadsUpManager();
     }
 
+    private NotificationEntry createStickyEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private NotificationEntry createStickyForSomeTimeEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private PendingIntent createFullScreenIntent() {
+        return PendingIntent.getActivity(
+                getContext(), 0, new Intent(getContext(), this.getClass()),
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+    }
+
+    private NotificationEntry createFullScreenIntentEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+
+    private void useAccessibilityTimeout(boolean use) {
+        if (use) {
+            doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+                    .getRecommendedTimeoutMillis(anyInt(), anyInt());
+        } else {
+            when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+                    i -> i.getArgument(0));
+        }
+    }
+
+
     @Before
     @Override
     public void setUp() {
         initMocks(this);
-        when(mEntry.getSbn()).thenReturn(mSbn);
-        when(mEntry.getKey()).thenReturn("entryKey");
-        when(mSbn.getNotification()).thenReturn(mNotification);
         super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
-                mAccessibilityMgr, mUiEventLoggerFake);
+
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+                TEST_A11Y_TIMEOUT_TIME);
     }
 
     @After
@@ -114,193 +165,327 @@
 
     @Test
     public void testHunRemovedLogging() {
-        mAlertEntry.mEntry = mEntry;
-        mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
-        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+        headsUpEntry.mEntry = notifEntry;
+
+        hum.onAlertEntryRemoved(headsUpEntry);
+
+        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = false;
 
-        assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = true;
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_noFSI_false() {
-        // Set up NotifEntry with no FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(entry));
     }
 
+
+    @Test
+    public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastJustAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+        verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+    }
+
+
+    @Test
+    public void testShowNotification_autoDismissesWithDefaultTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_sticky_neverAutoDismisses() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastLongestAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+        mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+        assertTrue("Should still be alerting after processing",
+                hum.isAlerting(entry.getKey()));
+    }
+
+
     @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
-        doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
-                .getRecommendedTimeoutMillis(anyInt(), anyInt());
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                        (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
 
-        TestableLooper.get(this).processMessages(2);
+        hum.showNotification(entry);
 
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Heads up should live long enough", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
+
+        hum.showNotification(entry);
+
+        final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+    }
+
+
+    @Test
+    public void testRemoveNotification_beforeMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // Try to remove but defer, since the notification has not been shown long enough.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), false /* releaseImmediately */);
+
+        assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+        assertTrue("HUN should still be alerting before minimum display time",
+                hum.isAlerting(entry.getKey()));
+
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+                "minimum display time");
+    }
+
+
+    @Test
+    public void testRemoveNotification_afterMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // After the minimum display time:
+        // 1. Check whether the notification is still alerting.
+        // 2. Try to remove it and check whether the remove succeeded.
+        // 3. Check whether it is still alerting after trying to remove it.
+        final Boolean[] livedPastMinimumDisplayTime = {null};
+        final Boolean[] removedAfterMinimumDisplayTime = {null};
+        final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+        final Runnable pastMinimumDisplayTimeRunnable = () -> {
+            livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+            removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+                    entry.getKey(), /* releaseImmediately = */ false);
+            livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+        };
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+        // Wait until the minimum display time has passed before attempting removal.
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("HUN should live past minimum display time",
+                livedPastMinimumDisplayTime[0]);
+        assertTrue("HUN should be removed immediately past minimum display time",
+                removedAfterMinimumDisplayTime[0]);
+        assertFalse("HUN should not live after being removed past minimum display time",
+                livedPastRemoveAfterMinimumDisplayTime[0]);
+        assertFalse(hum.isAlerting(entry.getKey()));
+    }
+
+
+    @Test
+    public void testRemoveNotification_releaseImmediately() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hum.showNotification(entry);
+
+        // Remove forcibly with releaseImmediately = true.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
+
+        assertTrue(removedImmediately);
+        assertFalse(hum.isAlerting(entry.getKey()));
     }
 
 
     @Test
     public void testIsSticky_rowPinnedAndExpanded_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
         when(mRow.isPinned()).thenReturn(true);
         notifEntry.setRow(mRow);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(true);
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_remoteInputActive_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.remoteInputActive = true;
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_hasFullScreenIntent_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
+
     @Test
-    public void testIsSticky_stickyAndNotDemoted_true() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder()
-                .setSbn(createStickySbn(0))
-                .build();
+    public void testIsSticky_stickyForSomeTime_false() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(alertEntry);
+        hum.showNotification(entry);
 
-        assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+        assertFalse(hum.isSticky(entry.getKey()));
     }
 
+
     @Test
     public void testIsSticky_false() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(false);
         headsUpEntry.remoteInputActive = false;
 
-        assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertFalse(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testCompareTo_withNullEntries() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(null, null)).isEqualTo(0);
     }
 
     @Test
     public void testCompareTo_withNonAlertEntries() {
-        NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
-        NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+        final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+                "nae1").build();
+        final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+                "nae2").build();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
     }
 
     @Test
     public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
         ongoingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setCategory(Notification.CATEGORY_CALL)
                                 .setOngoing(true)))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -309,20 +494,20 @@
 
     @Test
     public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
-        Person person = new Person.Builder().setName("person").build();
-        PendingIntent intent = mock(PendingIntent.class);
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+        final Person person = new Person.Builder().setName("person").build();
+        final PendingIntent intent = mock(PendingIntent.class);
         incomingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setStyle(Notification.CallStyle
                                         .forIncomingCall(person, intent, intent))))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -331,22 +516,18 @@
 
     @Test
     public void testPinEntry_logsPeek() {
-        // Needs full screen intent in order to be pinned
-        final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
-        entryToPin.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
-                        new Notification.Builder(mContext, "")
-                                .setFullScreenIntent(fullScreenIntent, true)))
-                .build());
+        // Needs full screen intent in order to be pinned
+        final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+        entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
         // Note: the standard way to show a notification would be calling showNotification rather
         // than onAlertEntryAdded. However, in practice showNotification in effect adds
         // the notification and then updates it; in order to not log twice, the entry needs
         // to have a functional ExpandableNotificationRow that can keep track of whether it's
         // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        mHeadsUpManager.onAlertEntryAdded(entryToPin);
+        hum.onAlertEntryAdded(entryToPin);
 
         assertEquals(1, mUiEventLoggerFake.numLogs());
         assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -355,14 +536,15 @@
 
     @Test
     public void testSetUserActionMayIndirectlyRemove() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
-        assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        hum.showNotification(notifEntry);
 
-        mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
-        assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+        hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+        assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5cabcd4..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -67,6 +68,8 @@
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
@@ -80,7 +83,8 @@
                 mLockPatternUtils,
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
-                mDumpManager);
+                mDumpManager,
+                mFeatureFlags);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -30,18 +32,36 @@
     fun create(
         scope: CoroutineScope,
         repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+        keyguardInteractor: KeyguardInteractor =
+            KeyguardInteractorFactory.create().keyguardInteractor,
+        fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+            mock<FromLockscreenTransitionInteractor>()
+        },
+        fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+            Lazy {
+                mock<FromPrimaryBouncerTransitionInteractor>()
+            },
     ): WithDependencies {
         return WithDependencies(
             repository = repository,
+            keyguardInteractor = keyguardInteractor,
+            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             KeyguardTransitionInteractor(
                 scope = scope,
                 repository = repository,
+                keyguardInteractor = { keyguardInteractor },
+                fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             )
         )
     }
 
     data class WithDependencies(
         val repository: KeyguardTransitionRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+        val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
         val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0829f31..dd45331 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -18,9 +18,11 @@
 
 import android.content.pm.UserInfo
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -123,6 +125,7 @@
         repository: SceneContainerRepository = fakeSceneContainerRepository()
     ): SceneInteractor {
         return SceneInteractor(
+            applicationScope = applicationScope(),
             repository = repository,
             logger = mock(),
         )
@@ -201,5 +204,18 @@
                 RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
                 RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
             )
+
+        fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
+            return when (this) {
+                DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Swipe ->
+                    DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
+                DomainLayerAuthenticationMethodModel.Password ->
+                    DataLayerAuthenticationMethodModel.Password
+                DomainLayerAuthenticationMethodModel.Pattern ->
+                    DataLayerAuthenticationMethodModel.Pattern
+            }
+        }
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 061b422..1827a5b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -54,8 +54,8 @@
 import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
 import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT;
 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER;
-import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN;
 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_PCC;
+import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN;
 import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED;
@@ -135,6 +135,7 @@
 import android.service.autofill.CompositeUserData;
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetEligibleReason;
+import android.service.autofill.Field;
 import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FieldClassificationUserData;
@@ -190,6 +191,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -1539,6 +1541,17 @@
                 return;
             }
 
+            if (mSessionFlags.mShowingSaveUi) {
+                // Even though the session has not yet been destroyed at this point, after the
+                // saveUi gets closed, the session will be destroyed and AutofillManager will reset
+                // its state. Processing the fill request will result in a great chance of corrupt
+                // state in Autofill.
+                Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
+                        + id + " is showing saveUi");
+                mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+                mFillResponseEventLogger.logAndEndEvent();
+                return;
+            }
 
             requestLog = mRequestLogs.get(requestId);
             if (requestLog != null) {
@@ -1817,11 +1830,11 @@
      */
     private static class DatasetComputationContainer {
         // List of all autofill ids that have a corresponding datasets
-        Set<AutofillId> mAutofillIds = new ArraySet<>();
+        Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
         // Set of datasets. Kept separately, to be able to be used directly for composing
         // FillResponse.
         Set<Dataset> mDatasets = new LinkedHashSet<>();
-        ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>();
 
         public String toString() {
             final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
@@ -1859,7 +1872,7 @@
                 // for now to keep safe. TODO(b/266379948): Revisit this logic.
 
                 Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
-                Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+                Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets);
                 c1.mAutofillIds.add(id);
                 c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
                 c1.mDatasets.addAll(copyDatasets);
@@ -1875,6 +1888,13 @@
         }
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on provider detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * PCC eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForProviderAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         @DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1886,9 +1906,9 @@
         }
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
-        ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>();
         Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-        Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+        Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
         for (Dataset dataset : response.getDatasets()) {
             if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
             @DatasetEligibleReason int pickReason = globalPickReason;
@@ -1964,7 +1984,7 @@
                 eligibleAutofillIds.add(id);
                 Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
                 if (datasetForIds == null) {
-                    datasetForIds = new ArraySet<>();
+                    datasetForIds = new LinkedHashSet<>();
                 }
                 datasetForIds.add(dataset);
                 autofillIdToDatasetMap.put(id, datasetForIds);
@@ -1975,23 +1995,30 @@
         container.mAutofillIds = eligibleAutofillIds;
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on PCC detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * provider eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForPccAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
 
         synchronized (mLock) {
-            ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> hintsToAutofillIdMap =
                     mClassificationState.mHintsToAutofillIdMap;
 
             // TODO(266379948): Handle group hints too.
-            ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> groupHintsToAutofillIdMap =
                     mClassificationState.mGroupHintsToAutofillIdMap;
 
-            ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+            Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>();
 
             Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-            Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+            Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
 
             for (int i = 0; i < datasets.size(); i++) {
 
@@ -2007,13 +2034,35 @@
                 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
                 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
                 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
-                Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+                Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>();
+
+                boolean isDatasetAvailable = false;
+                Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+                Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
 
                 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
                     if (dataset.getAutofillDatatypes().get(j) == null) {
+                        // TODO : revisit pickReason logic
                         if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
                             pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
                         }
+                        // Check if the autofill id at this index is detected by PCC.
+                        // If not, add that id here, otherwise, we can have duplicates when later
+                        // merging with provider datasets.
+                        // Howover, this doesn't make datasetAvailable for PCC on its own.
+                        // For that, there has to be a datatype detected by PCC, and the dataset
+                        // for that datatype provided by the provider.
+                        AutofillId autofillId = dataset.getFieldIds().get(j);
+                        if (!mClassificationState.mClassificationCombinedHintsMap
+                                .containsKey(autofillId)) {
+                            additionalEligibleAutofillIds.add(autofillId);
+                            additionalDatasetAutofillIds.add(autofillId);
+                            // For each of the field, copy over values.
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
+                        }
                         continue;
                     }
                     String hint = dataset.getAutofillDatatypes().get(j);
@@ -2021,22 +2070,18 @@
                     if (hintsToAutofillIdMap.containsKey(hint)) {
                         ArrayList<AutofillId> tempIds =
                                 new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+                        if (tempIds.isEmpty()) {
+                            continue;
+                        }
+                        isDatasetAvailable = true;
                         for (AutofillId autofillId : tempIds) {
                             eligibleAutofillIds.add(autofillId);
                             datasetAutofillIds.add(autofillId);
                             // For each of the field, copy over values.
-                            fieldIds.add(autofillId);
-                            fieldValues.add(dataset.getFieldValues().get(j));
-                            //  TODO(b/266379948): might need to make it more efficient by not
-                            //  copying over value if it didn't exist. This would require creating
-                            //  a getter for the presentations arraylist.
-                            fieldPresentations.add(dataset.getFieldPresentation(j));
-                            fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
-                            fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
-                            fieldInlineTooltipPresentations.add(
-                                    dataset.getFieldInlineTooltipPresentation(j));
-                            fieldFilters.add(dataset.getFilter(j));
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
                         }
                     }
                     // TODO(b/266379948):  handle the case:
@@ -2045,34 +2090,38 @@
                     // TODO(b/266379948):  also handle the case where there could be more types in
                     // the dataset, provided by the provider, however, they aren't applicable.
                 }
-                Dataset newDataset =
-                        new Dataset(
-                                fieldIds,
-                                fieldValues,
-                                fieldPresentations,
-                                fieldDialogPresentations,
-                                fieldInlinePresentations,
-                                fieldInlineTooltipPresentations,
-                                fieldFilters,
-                                new ArrayList<>(),
-                                dataset.getFieldContent(),
-                                null,
-                                null,
-                                null,
-                                null,
-                                dataset.getId(),
-                                dataset.getAuthentication());
-                newDataset.setEligibleReasonReason(pickReason);
-                eligibleDatasets.add(newDataset);
-                Set<Dataset> newDatasets;
-                for (AutofillId autofillId : datasetAutofillIds) {
-                    if (map.containsKey(autofillId)) {
-                        newDatasets = map.get(autofillId);
-                    } else {
-                        newDatasets = new ArraySet<>();
+                if (isDatasetAvailable) {
+                    datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+                    eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+                    Dataset newDataset =
+                            new Dataset(
+                                    fieldIds,
+                                    fieldValues,
+                                    fieldPresentations,
+                                    fieldDialogPresentations,
+                                    fieldInlinePresentations,
+                                    fieldInlineTooltipPresentations,
+                                    fieldFilters,
+                                    new ArrayList<>(),
+                                    dataset.getFieldContent(),
+                                    null,
+                                    null,
+                                    null,
+                                    null,
+                                    dataset.getId(),
+                                    dataset.getAuthentication());
+                    newDataset.setEligibleReasonReason(pickReason);
+                    eligibleDatasets.add(newDataset);
+                    Set<Dataset> newDatasets;
+                    for (AutofillId autofillId : datasetAutofillIds) {
+                        if (map.containsKey(autofillId)) {
+                            newDatasets = map.get(autofillId);
+                        } else {
+                            newDatasets = new LinkedHashSet<>();
+                        }
+                        newDatasets.add(newDataset);
+                        map.put(autofillId, newDatasets);
                     }
-                    newDatasets.add(newDataset);
-                    map.put(autofillId, newDatasets);
                 }
             }
             container.mAutofillIds = eligibleAutofillIds;
@@ -2081,6 +2130,31 @@
         }
     }
 
+    private void copyFieldsFromDataset(
+            Dataset dataset,
+            int index,
+            AutofillId autofillId,
+            ArrayList<AutofillId> fieldIds,
+            ArrayList<AutofillValue> fieldValues,
+            ArrayList<RemoteViews> fieldPresentations,
+            ArrayList<RemoteViews> fieldDialogPresentations,
+            ArrayList<InlinePresentation> fieldInlinePresentations,
+            ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+            ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+        // copy over values
+        fieldIds.add(autofillId);
+        fieldValues.add(dataset.getFieldValues().get(index));
+        //  TODO(b/266379948): might need to make it more efficient by not
+        //  copying over value if it didn't exist. This would require creating
+        //  a getter for the presentations arraylist.
+        fieldPresentations.add(dataset.getFieldPresentation(index));
+        fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+        fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+        fieldInlineTooltipPresentations.add(
+                dataset.getFieldInlineTooltipPresentation(index));
+        fieldFilters.add(dataset.getFilter(index));
+    }
+
     @GuardedBy("mLock")
     private void processNullResponseOrFallbackLocked(int requestId, int flags) {
         if (!mSessionFlags.mClientSuggestionsEnabled) {
@@ -2649,10 +2723,7 @@
                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
                     mClientState = newClientState;
                 }
-                Dataset dataset = (Dataset) result;
-                FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
-                temp = getEffectiveFillResponse(temp);
-                dataset = temp.getDatasets().get(0);
+                Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
                 if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -2678,6 +2749,39 @@
         }
     }
 
+    Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
+        FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
+        response = getEffectiveFillResponse(response);
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective response: " + response);
+        }
+        if (response == null || response.getDatasets().size() == 0) {
+            Log.wtf(TAG, "No datasets in fill response on authentication. response = "
+                    + (response == null ? "null" : response.toString()));
+            return authenticatedDataset;
+        }
+        List<Dataset> datasets = response.getDatasets();
+        Dataset result = response.getDatasets().get(0);
+        if (datasets.size() > 1) {
+            Dataset.Builder builder = new Dataset.Builder();
+            for (Dataset dataset : datasets) {
+                if (!dataset.getFieldIds().isEmpty()) {
+                    for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+                        builder.setField(dataset.getFieldIds().get(i),
+                                new Field.Builder().setValue(dataset.getFieldValues().get(i))
+                                        .build());
+                    }
+                }
+            }
+            result = builder.setId(authenticatedDataset.getId()).build();
+        }
+
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result);
+        }
+        return result;
+    }
+
     /**
      * Returns whether the dataset returned from the authentication result is ephemeral or not.
      * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e80cba7..996c68b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1026,6 +1026,17 @@
         }
 
         @Override
+        public byte[] getBackupPayload(int userId) {
+            // TODO(b/286124853): back up CDM data
+            return new byte[0];
+        }
+
+        @Override
+        public void applyRestoredPayload(byte[] payload, int userId) {
+            // TODO(b/286124853): restore CDM data
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index b7cb430..253ef43 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -39,10 +39,21 @@
           "include-filter": "android.hardware.input.cts.tests"
         },
         {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ],
       "file_patterns": ["Virtual[^/]*\\.java"]
+    },
+    {
+      "name": "CtsAccessibilityServiceTestCases",
+      "options": [
+        {
+          "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f263c8..c2774e5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -98,7 +98,10 @@
 
 java_library_static {
     name: "services.core.unboosted",
-    defaults: ["platform_service_defaults"],
+    defaults: [
+        "platform_service_defaults",
+        "android.hardware.power-java_static",
+    ],
     srcs: [
         ":android.hardware.biometrics.face-V3-java-source",
         ":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -152,7 +155,7 @@
         "android.hardware.boot-V1.0-java", // HIDL
         "android.hardware.boot-V1.1-java", // HIDL
         "android.hardware.boot-V1.2-java", // HIDL
-        "android.hardware.boot-V1-java",   // AIDL
+        "android.hardware.boot-V1-java", // AIDL
         "android.hardware.broadcastradio-V2.0-java", // HIDL
         "android.hardware.broadcastradio-V1-java", // AIDL
         "android.hardware.health-V1.0-java", // HIDL
@@ -176,7 +179,6 @@
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
-        "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "cbor-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 36ef9b7..cb246f6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2501,12 +2501,12 @@
                         FGS_STOP_REASON_STOP_FOREGROUND,
                         FGS_TYPE_POLICY_CHECK_UNKNOWN);
 
-                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
-                // earlier.
-                r.foregroundServiceType = 0;
                 synchronized (mFGSLogger) {
                     mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r);
                 }
+                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+                // earlier.
+                r.foregroundServiceType = 0;
                 r.mFgsNotificationWasDeferred = false;
                 signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c0ac1f8..d3bfc9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -764,37 +764,37 @@
                     out.println(
                             "Error: Activity not started, unable to "
                                     + "resolve " + intent.toString());
-                    break;
+                    return 1;
                 case ActivityManager.START_CLASS_NOT_FOUND:
                     out.println(NO_CLASS_ERROR_CODE);
                     out.println("Error: Activity class " +
                             intent.getComponent().toShortString()
                             + " does not exist.");
-                    break;
+                    return 1;
                 case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                     out.println(
                             "Error: Activity not started, you requested to "
                                     + "both forward and receive its result");
-                    break;
+                    return 1;
                 case ActivityManager.START_PERMISSION_DENIED:
                     out.println(
                             "Error: Activity not started, you do not "
                                     + "have permission to access it.");
-                    break;
+                    return 1;
                 case ActivityManager.START_NOT_VOICE_COMPATIBLE:
                     out.println(
                             "Error: Activity not started, voice control not allowed for: "
                                     + intent);
-                    break;
+                    return 1;
                 case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
                     out.println(
                             "Error: Not allowed to start background user activity"
                                     + " that shouldn't be displayed for all users.");
-                    break;
+                    return 1;
                 default:
                     out.println(
                             "Error: Activity not started, unknown error code " + res);
-                    break;
+                    return 1;
             }
             out.flush();
             if (mWaitOption && launched) {
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 9fdb833..e84fed7 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -163,6 +164,12 @@
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
                 TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 786e1cc..f6859d1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -141,6 +141,10 @@
         // grab the appropriate types
         final IntArray apiTypes =
                 convertFgsTypeToApiTypes(record.foregroundServiceType);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "Foreground service start for UID: "
+                    + uid + " does not have any types");
+        }
         // now we need to iterate through the types
         // and insert the new record as needed
         final IntArray apiTypesFound = new IntArray();
@@ -201,6 +205,9 @@
         // and also clean up the start calls stack by UID
         final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "FGS stop call for: " + uid + " has no types!");
+        }
         if (uidState == null) {
             Slog.w(TAG, "FGS stop call being logged with no start call for UID for UID "
                     + uid
@@ -460,16 +467,17 @@
     public void logFgsApiEvent(ServiceRecord r, int fgsState,
             @FgsApiState int apiState,
             @ForegroundServiceApiType int apiType, long timestamp) {
-        long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
-        long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+        long apiDurationBeforeFgsStart = 0;
+        long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(r.appInfo.uid);
-        if (uidState != null) {
-            if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
-                apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
-            }
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+            apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
@@ -525,10 +533,11 @@
             @ForegroundServiceApiType int apiType, long timestamp) {
         long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(uid);
-        if (uidState != null) {
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f7bbc8b..8a8e2af 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -286,6 +286,12 @@
                 + ")");
     }
 
+    private String getFgsInfoForWtf() {
+        return " cmp: " + this.getComponentName().toShortString()
+                + " sdk: " + this.appInfo.targetSdkVersion
+                ;
+    }
+
     void maybeLogFgsLogicChange() {
         final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
                 mAllowWIUInBindService);
@@ -311,7 +317,8 @@
                 + " OS:" // Orig-start
                 + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
                 + " NS:" // New-start
-                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+                + getFgsInfoForWtf();
         Slog.wtf(TAG_SERVICE, message);
     }
 
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 80d14a2..81397b4 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -129,8 +129,6 @@
     private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING";
     private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING";
 
-    private static final boolean DEBUG = false;
-
     static final int WRITE_SETTINGS = 1;
     static final int REMOVE_SETTINGS = 2;
     static final int POPULATE_GAME_MODE_SETTINGS = 3;
@@ -407,6 +405,7 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             final String[] packageNames = properties.getKeyset().toArray(new String[0]);
+            Slog.v(TAG, "Device config changed for packages: " + Arrays.toString(packageNames));
             updateConfigsForUser(ActivityManager.getCurrentUser(), true /*checkGamePackage*/,
                     packageNames);
         }
@@ -1861,15 +1860,11 @@
                     final GamePackageConfiguration config =
                             new GamePackageConfiguration(mPackageManager, packageName, userId);
                     if (config.isActive()) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Adding config: " + config.toString());
-                        }
+                        Slog.v(TAG, "Adding config: " + config.toString());
                         mConfigs.put(packageName, config);
                     } else {
-                        if (DEBUG) {
-                            Slog.w(TAG, "Inactive package config for "
+                        Slog.v(TAG, "Inactive package config for "
                                     + config.getPackageName() + ":" + config.toString());
-                        }
                         mConfigs.remove(packageName);
                     }
                 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index af8aa916..d89171d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -435,7 +435,7 @@
         // LE Audio it stays the same and we must trigger the proper stream volume alignment, if
         // LE Audio communication device is activated after the audio system has already switched to
         // MODE_IN_CALL mode.
-        if (isBluetoothLeAudioRequested()) {
+        if (isBluetoothLeAudioRequested() && device != null) {
             final int streamType = mAudioService.getBluetoothContextualVolumeStream();
             final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
             final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5a92cb4..13e3fc7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -2070,12 +2070,18 @@
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceAvailable(
             AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
-        final String address = btInfo.mDevice.getAddress();
-        final String name = BtHelper.getName(btInfo.mDevice);
         final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
         final int device = btInfo.mAudioSystemDevice;
 
         if (device != AudioSystem.DEVICE_NONE) {
+            final String address = btInfo.mDevice.getAddress();
+            String name = BtHelper.getName(btInfo.mDevice);
+
+            // The BT Stack does not provide a name for LE Broadcast devices
+            if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
+                name = "Broadcast";
+            }
+
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 851c5c3..1578193 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -334,9 +334,8 @@
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
-        // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
-        // mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-        //        SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
     }
 
     float getOutputRs2UpperBound() {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b5d5cbe..de4979a 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -61,6 +61,7 @@
     private static final String PEOPLE_HELPER = "people";
     private static final String APP_LOCALES_HELPER = "app_locales";
     private static final String APP_GENDER_HELPER = "app_gender";
+    private static final String COMPANION_HELPER = "companion";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -95,7 +96,8 @@
                     PERMISSION_HELPER,
                     NOTIFICATION_HELPER,
                     SYNC_SETTINGS_HELPER,
-                    APP_LOCALES_HELPER);
+                    APP_LOCALES_HELPER,
+                    COMPANION_HELPER);
 
     /** Helpers that are enabled for full, non-system users. */
     private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -132,6 +134,7 @@
         addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
         addHelperIfEligibleForUser(APP_GENDER_HELPER,
                 new AppGrammaticalGenderBackupHelper(mUserId));
+        addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cb5e7f1..2ae3118 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -150,6 +150,10 @@
     // Timestamp when hardware authentication occurred
     private long mAuthenticatedTimeMs;
 
+    @NonNull
+    private final OperationContextExt mOperationContext;
+
+
     AuthSession(@NonNull Context context,
             @NonNull BiometricContext biometricContext,
             @NonNull IStatusBarService statusBarService,
@@ -215,6 +219,7 @@
         mFingerprintSensorProperties = fingerprintSensorProperties;
         mCancelled = false;
         mBiometricFrameworkStatsLogger = logger;
+        mOperationContext = new OperationContextExt(true /* isBP */);
 
         try {
             mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -581,6 +586,8 @@
         } else {
             Slog.d(TAG, "delaying fingerprint sensor start");
         }
+
+        mBiometricContext.updateContext(mOperationContext, isCrypto());
     }
 
     // call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -743,12 +750,12 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
                         + ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
 
             mBiometricFrameworkStatsLogger.authenticate(
-                    mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                            isCrypto()),
+                    mOperationContext,
                     statsModality(),
                     BiometricsProtoEnums.ACTION_UNKNOWN,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -780,13 +787,13 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", Reason: " + reason
                         + ", Error: " + error
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
             // Auth canceled
             if (error != 0) {
                 mBiometricFrameworkStatsLogger.error(
-                        mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                                isCrypto()),
+                        mOperationContext,
                         statsModality(),
                         BiometricsProtoEnums.ACTION_AUTHENTICATE,
                         BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..137a418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+    private final int mUserId;
+    private int mTotalAttempts;
+    private int mRejectedAttempts;
+    private int mEnrollmentNotifications;
+    private final int mModality;
+
+    public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = totalAttempts;
+        mRejectedAttempts = rejectedAttempts;
+        mEnrollmentNotifications = enrollmentNotifications;
+        mModality = modality;
+    }
+
+    public AuthenticationStats(final int userId, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+        mEnrollmentNotifications = 0;
+        mModality = modality;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public int getTotalAttempts() {
+        return mTotalAttempts;
+    }
+
+    public int getRejectedAttempts() {
+        return mRejectedAttempts;
+    }
+
+    public int getEnrollmentNotifications() {
+        return mEnrollmentNotifications;
+    }
+
+    public int getModality() {
+        return mModality;
+    }
+
+    /** Calculate FRR. */
+    public float getFrr() {
+        if (mTotalAttempts > 0) {
+            return mRejectedAttempts / (float) mTotalAttempts;
+        } else {
+            return -1.0f;
+        }
+    }
+
+    /** Update total authentication attempts and rejections. */
+    public void authenticate(boolean authenticated) {
+        if (!authenticated) {
+            mRejectedAttempts++;
+        }
+        mTotalAttempts++;
+    }
+
+    /** Reset total authentication attempts and rejections. */
+    public void resetData() {
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..c9cd814
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+    private static final String TAG = "AuthenticationStatsCollector";
+
+    // The minimum number of attempts that will calculate the FRR and trigger the notification.
+    private static final int MINIMUM_ATTEMPTS = 500;
+    // The maximum number of eligible biometric enrollment notification can be sent.
+    private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+    private final float mThreshold;
+    private final int mModality;
+
+    @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+    public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+        mThreshold = context.getResources()
+                .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+        mUserAuthenticationStatsMap = new HashMap<>();
+        mModality = modality;
+    }
+
+    /** Update total authentication and rejected attempts. */
+    public void authenticate(int userId, boolean authenticated) {
+        // Check if this is a new user.
+        if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+            mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+        }
+
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+        authenticationStats.authenticate(authenticated);
+
+        persistDataIfNeeded(userId);
+        sendNotificationIfNeeded(userId);
+    }
+
+    private void sendNotificationIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
+            // Send notification if FRR exceeds the threshold
+            if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
+                    && authenticationStats.getFrr() >= mThreshold) {
+                // TODO(wenhuiy): Send notifications.
+            }
+
+            authenticationStats.resetData();
+        }
+    }
+
+    private void persistDataIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() % 50 == 0) {
+            // TODO(wenhuiy): Persist data.
+        }
+    }
+
+    /**
+     * Only being used in tests. Callers should not make any changes to the returned
+     * authentication stats.
+     *
+     * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+     */
+    @Nullable
+    @VisibleForTesting
+    AuthenticationStats getAuthenticationStatsForUser(int userId) {
+        return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+    }
+
+    @VisibleForTesting
+    void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+        mUserAuthenticationStatsMap.put(userId, authenticationStats);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 
 /**
@@ -41,6 +42,7 @@
     private final int mStatsAction;
     private final int mStatsClient;
     private final BiometricFrameworkStatsLogger mSink;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @NonNull private final ALSProbe mALSProbe;
 
     private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
     /** Get a new logger with all unknown fields (for operations that do not require logs). */
     public static BiometricLogger ofUnknown(@NonNull Context context) {
         return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+                null /* AuthenticationStatsCollector */);
     }
 
     /**
@@ -64,26 +67,32 @@
      * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
      */
     public BiometricLogger(
-            @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+            @NonNull Context context, int statsModality, int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         this(statsModality, statsAction, statsClient,
                 BiometricFrameworkStatsLogger.getInstance(),
+                authenticationStatsCollector,
                 context.getSystemService(SensorManager.class));
     }
 
     @VisibleForTesting
     BiometricLogger(
             int statsModality, int statsAction, int statsClient,
-            BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+            BiometricFrameworkStatsLogger logSink,
+            @NonNull AuthenticationStatsCollector statsCollector,
+            SensorManager sensorManager) {
         mStatsModality = statsModality;
         mStatsAction = statsAction;
         mStatsClient = statsClient;
         mSink = logSink;
+        mAuthenticationStatsCollector = statsCollector;
         mALSProbe = new ALSProbe(sensorManager);
     }
 
     /** Creates a new logger with the action replaced with the new action. */
     public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
-        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+                null /* AuthenticationStatsCollector */);
     }
 
     /** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
     public void logOnAuthenticated(Context context, OperationContextExt operationContext,
             boolean authenticated, boolean requireConfirmation, int targetUserId,
             boolean isBiometricPrompt) {
+        // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
         if (!mShouldLogMetrics) {
             return;
         }
 
+        mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
         int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
         if (!authenticated) {
             authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
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 33ed63c..a7d160c 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
@@ -49,6 +49,7 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -112,6 +113,8 @@
     private final BiometricContext mBiometricContext;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
     private IFace mDaemon;
 
@@ -173,6 +176,9 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE);
+
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
 
@@ -283,7 +289,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
 
@@ -341,7 +348,8 @@
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
@@ -372,7 +380,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -386,7 +395,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token, userId,
                     opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -407,7 +417,8 @@
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                             mBiometricStateCallback, new ClientMonitorCallback() {
@@ -443,7 +454,8 @@
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(),
                     token, id, callback, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -471,7 +483,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -540,7 +553,8 @@
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -554,7 +568,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -624,7 +639,8 @@
                             mFaceSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FaceUtils.getInstance(sensorId),
                             mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -636,9 +652,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
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 1e33c96..10991d5 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
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -124,6 +125,7 @@
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE);
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -554,7 +559,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, userId, opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -617,7 +622,7 @@
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +682,8 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, requestId, receiver, operationId, restricted,
                     options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
                     mUsageStats, allowBackgroundAuthentication,
                     Utils.getCurrentStrength(mSensorId));
@@ -713,7 +719,7 @@
                     new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -731,7 +737,7 @@
                     opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -750,7 +756,7 @@
             final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -821,7 +827,7 @@
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +959,7 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
                 createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN),
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                 mBiometricContext, hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -968,9 +974,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     /**
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 0421d78..2d062db 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
@@ -57,6 +57,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -122,6 +123,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -181,6 +183,9 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
         for (SensorProps prop : props) {
@@ -338,7 +343,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
@@ -363,7 +369,8 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -380,7 +387,7 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                             mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -395,7 +402,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             userId, opPackageName, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -415,7 +423,7 @@
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
@@ -455,7 +463,8 @@
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -477,7 +486,8 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController,
@@ -566,7 +576,8 @@
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -588,7 +599,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FingerprintUtils.getInstance(sensorId),
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -600,9 +612,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
@@ -635,7 +648,8 @@
                     new FingerprintInvalidationClient(mContext,
                             mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
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 92b216d..4b07dca 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
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -123,6 +124,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -351,6 +353,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -497,7 +502,8 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
                         mBiometricContext,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -544,7 +550,8 @@
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
                     userId, mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -559,7 +566,8 @@
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             mSensorProperties.sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -573,7 +581,8 @@
                     mContext, mLazyDaemon, token, userId, opPackageName,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -594,7 +603,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -639,7 +648,8 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -660,7 +670,8 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, requestId, listener, operationId,
                     restricted, options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController,
@@ -706,7 +717,7 @@
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -726,7 +737,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -741,7 +752,7 @@
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
@@ -762,9 +773,10 @@
                 mBiometricStateCallback));
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index da51569..1c1b69b 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -134,6 +134,13 @@
     }
 
     /**
+     * Helper methods to create builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
      * A DisplayBrightnessState's builder class.
      */
     public static class Builder {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 58f4d08..fe445a6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1547,6 +1547,7 @@
                 if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
                     mDisplayWindowPolicyControllers.put(
                             displayId, Pair.create(virtualDevice, dwpc));
+                    Slog.d(TAG, "Virtual Display: successfully created virtual display");
                 }
             }
 
@@ -1593,19 +1594,25 @@
                     if (!getProjectionService().setContentRecordingSession(session, projection)) {
                         // Unable to start mirroring, so release VirtualDisplay. Projection service
                         // handles stopping the projection.
+                        Slog.w(TAG, "Content Recording: failed to start mirroring - "
+                                + "releasing virtual display " + displayId);
                         releaseVirtualDisplayInternal(callback.asBinder());
                         return Display.INVALID_DISPLAY;
                     } else if (projection != null) {
                         // Indicate that this projection has been used to record, and can't be used
                         // again.
+                        Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+                                + " VirtualDisplay creation.");
                         projection.notifyVirtualDisplayCreated(displayId);
                     }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
                             + "content recording session", e);
+                    return displayId;
                 }
+                Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+                        + displayId);
             }
-
             return displayId;
         } finally {
             Binder.restoreCallingIdentity(secondToken);
@@ -1629,10 +1636,13 @@
             return -1;
         }
 
+
+        Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
         DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
                 callback, projection, callingUid, packageName, surface, flags,
                 virtualDisplayConfig);
         if (device == null) {
+            Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
             return -1;
         }
 
@@ -1710,6 +1720,7 @@
 
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+            Slog.d(TAG, "Virtual Display: Display Device released");
             if (device != null) {
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 5213d31..0f89a6e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -254,13 +254,6 @@
     // The doze screen brightness.
     private final float mScreenBrightnessDozeConfig;
 
-    // The dim screen brightness.
-    private final float mScreenBrightnessDimConfig;
-
-    // The minimum dim amount to use if the screen brightness is already below
-    // mScreenBrightnessDimConfig.
-    private final float mScreenBrightnessMinimumDimAmount;
-
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
@@ -349,7 +342,7 @@
     private boolean mDozing;
 
     private boolean mAppliedDimming;
-    private boolean mAppliedLowPower;
+
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -529,11 +522,6 @@
         // DOZE AND DIM SETTINGS
         mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
-        mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
-        mScreenBrightnessMinimumDimAmount = resources.getFloat(
-                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
-
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
@@ -565,7 +553,7 @@
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
                 mDisplayDeviceConfig
-        ));
+        ), mContext);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -1426,6 +1414,7 @@
         // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
         // we broadcast this change through setting.
         final float unthrottledBrightnessState = brightnessState;
+
         if (mBrightnessThrottler.isThrottled()) {
             mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
             brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
@@ -1449,42 +1438,12 @@
             mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessState);
         }
 
-        // Apply dimming by at least some minimum amount when user activity
-        // timeout is about to expire.
-        if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                brightnessState = Math.max(
-                        Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
-                                mScreenBrightnessDimConfig),
-                        PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
-            }
-            if (!mAppliedDimming) {
-                slowChange = false;
-            }
-            mAppliedDimming = true;
-        } else if (mAppliedDimming) {
-            slowChange = false;
-            mAppliedDimming = false;
-        }
-        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
-        // as long as it is above the minimum threshold.
-        if (mPowerRequest.lowPowerMode) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                final float brightnessFactor =
-                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
-                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
-                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
-            }
-            if (!mAppliedLowPower) {
-                slowChange = false;
-            }
-            mAppliedLowPower = true;
-        } else if (mAppliedLowPower) {
-            slowChange = false;
-            mAppliedLowPower = false;
-        }
+        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+                brightnessState, slowChange);
+
+        brightnessState = clampedState.getBrightness();
+        slowChange = clampedState.isSlowChange();
+        mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         // The current brightness to use has been calculated at this point, and HbmController should
         // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
@@ -1542,8 +1501,6 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
-            animateValue = mBrightnessClamperController.clamp(animateValue);
-
             // If there are any HDR layers on the screen, we have a special brightness value that we
             // use instead. We still preserve the calculated brightness for Standard Dynamic Range
             // (SDR) layers, but the main brightness value will be the one for HDR.
@@ -2379,7 +2336,6 @@
         pw.println();
         pw.println("Display Power Controller Configuration:");
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
-        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
@@ -2411,7 +2367,6 @@
         pw.println("  mPowerRequest=" + mPowerRequest);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
-        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6191861..6936112 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,9 +147,12 @@
         try {
             if (projection != null) {
                 projection.registerCallback(mediaProjectionCallback);
+                Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+                        + "VirtualDisplayDevice");
             }
             appToken.linkToDeath(device, 0);
         } catch (RemoteException ex) {
+            Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
             mVirtualDisplayDevices.remove(appToken);
             device.destroyLocked(false);
             return null;
@@ -445,6 +448,7 @@
         }
 
         public void stopLocked() {
+            Slog.d(TAG, "Virtual Display: stopping device " + mName);
             setSurfaceLocked(null);
             mStopped = true;
         }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 9345a3d..54a280f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -21,6 +21,9 @@
 
 import java.io.PrintWriter;
 
+/**
+ * Provides max allowed brightness
+ */
 abstract class BrightnessClamper<T> {
 
     protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index d0f28c3..9b28989 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -20,6 +20,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
@@ -28,6 +30,7 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
@@ -42,7 +45,7 @@
  */
 public class BrightnessClamperController {
 
-    private static final boolean ENABLED = false;
+    private static final boolean THERMAL_ENABLED = false;
 
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
@@ -50,6 +53,8 @@
 
     private final Executor mExecutor;
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+
+    private final List<BrightnessModifier> mModifiers = new ArrayList<>();
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -57,13 +62,13 @@
     private Type mClamperType = null;
 
     public BrightnessClamperController(Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
-        this(new Injector(), handler, clamperChangeListener, data);
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+        this(new Injector(), handler, clamperChangeListener, data, context);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
         mClamperChangeListenerExternal = clamperChangeListener;
@@ -77,11 +82,13 @@
             }
         };
 
-        if (ENABLED) {
+        if (THERMAL_ENABLED) {
             mClampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
-            start();
         }
+        mModifiers.add(new DisplayDimModifier(context));
+        mModifiers.add(new BrightnessLowPowerModeModifier());
+        start();
     }
 
     /**
@@ -95,8 +102,19 @@
      * Applies clamping
      * Called in DisplayControllerHandler
      */
-    public float clamp(float value) {
-        return Math.min(value, mBrightnessCap);
+    public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+            float brightnessValue, boolean slowChange) {
+        float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
+
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setIsSlowChange(slowChange);
+        builder.setBrightness(cappedBrightness);
+
+        for (int i = 0; i < mModifiers.size(); i++) {
+            mModifiers.get(i).apply(request, builder);
+        }
+
+        return builder.build();
     }
 
     /**
@@ -108,6 +126,7 @@
         writer.println("  mClamperType: " + mClamperType);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
         mClampers.forEach(clamper -> clamper.dump(ipw));
+        mModifiers.forEach(modifier -> modifier.dump(ipw));
     }
 
     /**
@@ -144,8 +163,10 @@
     }
 
     private void start() {
-        mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
-                mExecutor, mOnPropertiesChangedListener);
+        if (!mClampers.isEmpty()) {
+            mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+                    mExecutor, mOnPropertiesChangedListener);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
new file mode 100644
index 0000000..b478952
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+class BrightnessLowPowerModeModifier extends BrightnessModifier {
+
+    @Override
+    boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request) {
+        return request.lowPowerMode;
+    }
+
+
+    @Override
+    float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request) {
+        final float brightnessFactor =
+                Math.min(request.screenLowPowerBrightnessFactor, 1);
+        return Math.max((currentBrightness * brightnessFactor), PowerManager.BRIGHTNESS_MIN);
+    }
+
+    @Override
+    int getModifier() {
+        return BrightnessReason.MODIFIER_LOW_POWER;
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessLowPowerModeModifier:");
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        super.dump(ipw);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
new file mode 100644
index 0000000..112e63d
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * Modifies current brightness based on request
+ */
+abstract class BrightnessModifier {
+
+    private boolean mApplied = false;
+
+    abstract boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request);
+
+    abstract float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request);
+
+    abstract int getModifier();
+
+    void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+        // as long as it is above the minimum threshold.
+        if (shouldApply(request)) {
+            float value = stateBuilder.getBrightness();
+            if (value > PowerManager.BRIGHTNESS_MIN) {
+                stateBuilder.setBrightness(getBrightnessAdjusted(value, request));
+                stateBuilder.getBrightnessReason().addModifier(getModifier());
+            }
+            if (!mApplied) {
+                stateBuilder.setIsSlowChange(false);
+            }
+            mApplied = true;
+        } else if (mApplied) {
+            stateBuilder.setIsSlowChange(false);
+            mApplied = false;
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("BrightnessModifier:");
+        pw.println("  mApplied=" + mApplied);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
new file mode 100644
index 0000000..4ff7bdb
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.R;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+class DisplayDimModifier extends BrightnessModifier {
+
+    // The dim screen brightness.
+    private final float mScreenBrightnessDimConfig;
+
+    // The minimum dim amount to use if the screen brightness is already below
+    // mScreenBrightnessDimConfig.
+    private final float mScreenBrightnessMinimumDimAmount;
+
+    DisplayDimModifier(Context context) {
+        PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
+        Resources resources = context.getResources();
+
+        mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+        mScreenBrightnessMinimumDimAmount = resources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+    }
+
+
+    @Override
+    boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request) {
+        return request.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+    }
+
+    @Override
+    float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request) {
+        return Math.max(
+                Math.min(currentBrightness - mScreenBrightnessMinimumDimAmount,
+                        mScreenBrightnessDimConfig),
+                PowerManager.BRIGHTNESS_MIN);
+    }
+
+    @Override
+    int getModifier() {
+        return BrightnessReason.MODIFIER_DIMMED;
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("DisplayDimModifier:");
+        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+        pw.println("  mScreenBrightnessMinimumDimAmount=" + mScreenBrightnessMinimumDimAmount);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        super.dump(ipw);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 7ae7d5c..53c0217 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -222,6 +222,21 @@
         );
     }
 
+    /**
+     * Writes a HdmiSoundbarModeStatusReported atom representing a Dynamic soundbar mode status
+     * change.
+     * @param isSupported         Whether the hardware supports ARC.
+     * @param isEnabled           Whether DSM is enabled.
+     * @param enumLogReason       The event that triggered the log.
+     */
+    public void dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.HDMI_SOUNDBAR_MODE_STATUS_REPORTED,
+                isSupported,
+                isEnabled,
+                enumLogReason);
+    }
+
     private int earcStateToEnum(int earcState) {
         switch (earcState) {
             case HDMI_EARC_STATUS_IDLE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dd45307..5abb2b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -694,13 +694,13 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
 
         mSoundbarModeFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, true);
         mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, true);
         mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, true);
         mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, true);
 
         synchronized (mLock) {
             mEarcEnabled = (mHdmiCecConfig.getIntValue(
@@ -857,7 +857,7 @@
                         public void onPropertiesChanged(DeviceConfig.Properties properties) {
                             mEarcTxFeatureFlagEnabled = properties.getBoolean(
                                     Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX,
-                                    false);
+                                    true);
                             boolean earcEnabledSetting = mHdmiCecConfig.getIntValue(
                                     HdmiControlManager.SETTING_NAME_EARC_ENABLED)
                                     == EARC_FEATURE_ENABLED;
@@ -891,7 +891,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mSoundbarModeFeatureFlagEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
-                                false);
+                                true);
                         boolean soundbarModeSetting = mHdmiCecConfig.getIntValue(
                                 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
                                 == SOUNDBAR_MODE_ENABLED;
@@ -917,7 +917,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mTransitionFromArcToEarcTxEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX,
-                                false);
+                                true);
                     }
                 });
 
@@ -927,7 +927,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI,
-                                false);
+                                true);
                         checkAndUpdateAbsoluteVolumeBehavior();
                     }
                 });
@@ -1064,13 +1064,18 @@
      */
     @VisibleForTesting
     public void setSoundbarMode(final int settingValue) {
+        boolean isArcSupported = isArcSupported();
         HdmiCecLocalDevicePlayback playback = playback();
         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+        getAtomWriter().dsmStatusChanged(isArcSupported,
+                settingValue == SOUNDBAR_MODE_ENABLED,
+                HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED);
+
         if (playback == null) {
             Slog.w(TAG, "Device type not compatible to change soundbar mode.");
             return;
         }
-        if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+        if (!isArcSupported) {
             Slog.w(TAG, "Device type doesn't support ARC.");
             return;
         }
@@ -1269,11 +1274,8 @@
     @ServiceThreadOnly
     private List<Integer> getCecLocalDeviceTypes() {
         ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
-        if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
-                == SOUNDBAR_MODE_ENABLED
-                && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
-                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
-                && mSoundbarModeFeatureFlagEnabled) {
+        if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+                && isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
             allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         }
         return allLocalDeviceTypes;
@@ -3589,6 +3591,16 @@
         }
     }
 
+    private boolean isDsmEnabled() {
+        return mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                == SOUNDBAR_MODE_ENABLED;
+    }
+
+    @VisibleForTesting
+    protected boolean isArcSupported() {
+        return SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
+    }
+
     @ServiceThreadOnly
     int getPowerStatus() {
         assertRunOnServiceThread();
@@ -3705,6 +3717,9 @@
             int earcStatus = getEarcStatus();
             getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
                     earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE);
+        } else if (isPlaybackDevice()) {
+            getAtomWriter().dsmStatusChanged(isArcSupported(), isDsmEnabled(),
+                    HdmiStatsEnums.LOG_REASON_DSM_WAKE);
         }
         // TODO: Initialize MHL local devices.
     }
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 4b30ae5..08e5977 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -21,10 +21,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
 import android.icu.util.ULocale;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -41,9 +41,7 @@
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -59,6 +57,7 @@
 
     @Retention(SOURCE)
     @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
+            LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
             LAYOUT_SELECTION_CRITERIA_USER,
             LAYOUT_SELECTION_CRITERIA_DEVICE,
             LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
@@ -67,23 +66,26 @@
     public @interface LayoutSelectionCriteria {
     }
 
+    /** Unspecified layout selection criteria */
+    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
     /** Manual selection by user */
-    public static final int LAYOUT_SELECTION_CRITERIA_USER = 0;
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
 
     /** Auto-detection based on device provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 1;
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
 
     /** Auto-detection based on IME provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
 
     /** Default selection */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
 
     @VisibleForTesting
-    static final String DEFAULT_LAYOUT = "Default";
+    static final String DEFAULT_LAYOUT_NAME = "Default";
 
     @VisibleForTesting
-    static final String DEFAULT_LANGUAGE_TAG = "None";
+    public static final String DEFAULT_LANGUAGE_TAG = "None";
 
     public enum KeyboardLogEvent {
         UNSPECIFIED(
@@ -536,23 +538,23 @@
                             mLayoutSelectionCriteriaList.get(i);
                     InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
                     String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
-                    keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
-                            : keyboardLanguageTag;
+                    keyboardLanguageTag = TextUtils.isEmpty(keyboardLanguageTag)
+                            ? DEFAULT_LANGUAGE_TAG : keyboardLanguageTag;
                     int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
                             mInputDevice.getKeyboardLayoutType());
 
                     ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
-                    String canonicalizedLanguageTag =
-                            imeSubtype.getCanonicalizedLanguageTag().equals("")
-                            ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
                     String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
-                            : canonicalizedLanguageTag;
+                            : imeSubtype.getCanonicalizedLanguageTag();
+                    imeLanguageTag = TextUtils.isEmpty(imeLanguageTag) ? DEFAULT_LANGUAGE_TAG
+                            : imeLanguageTag;
                     int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
                             imeSubtype.getPhysicalKeyboardHintLayoutType());
 
                     // Sanitize null values
                     String keyboardLayoutName =
-                            selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
+                            selectedLayout == null ? DEFAULT_LAYOUT_NAME
+                                    : selectedLayout.getLabel();
 
                     configurationList.add(
                             new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
@@ -601,6 +603,8 @@
     private static String getStringForSelectionCriteria(
             @LayoutSelectionCriteria int layoutSelectionCriteria) {
         switch (layoutSelectionCriteria) {
+            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+                return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
             case LAYOUT_SELECTION_CRITERIA_USER:
                 return "LAYOUT_SELECTION_CRITERIA_USER";
             case LAYOUT_SELECTION_CRITERIA_DEVICE:
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b3ef869..c3abfc1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -192,9 +192,40 @@
 import javax.crypto.spec.GCMParameterSpec;
 
 /**
- * Keeps the lock pattern/password data and related settings for each user. Used by
- * LockPatternUtils. Needs to be a service because Settings app also needs to be able to save
- * lockscreen information for secondary users.
+ * LockSettingsService (LSS) mainly has the following responsibilities:
+ * <p>
+ * <ul>
+ *   <li>Provide APIs to verify and change the Lock Screen Knowledge Factor (LSKF) ("lockscreen
+ *   credential") of each user.  Unlock users when their correct LSKF is given.</li>
+ *
+ *   <li>Store other lockscreen related settings, such as some Keyguard (UI) settings.</li>
+ *
+ *   <li>Manage each user's synthetic password (SP), which is their main cryptographic secret.
+ *   See {@link SyntheticPasswordManager}.</li>
+ *
+ *   <li>Protect each user's SP using their LSKF.  Use the Gatekeeper or Weaver HAL to ensure that
+ *   guesses of the LSKF are ratelimited by the TEE or secure element.</li>
+ *
+ *   <li>Protect each user's data using their SP.  For example, use the SP to encrypt/decrypt the
+ *   user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
+ *
+ *   <li>Generate, protect, and use profile passwords for managed profiles.</li>
+ *
+ *   <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
+ *   OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
+ *
+ *   <li>Implement part of the Factory Reset Protection (FRP) and Repair Mode features by storing
+ *   the information needed to verify a user's LSKF on the persist or metadata partition.</li>
+ *
+ *   <li>Support insider attack resistance using the AuthSecret HAL.</li>
+ *
+ *   <li>Implement "recoverable keystore", a feature that enables end-to-end encrypted backups.
+ *   See {@link android.security.keystore.recovery.RecoveryController}.</li>
+ * </ul>
+ * <p>
+ * The main clients of LockSettingsService are Keyguard (i.e. the lockscreen UI, which is part of
+ * System UI), the Settings app (com.android.settings), and other parts of system_server.  Most
+ * methods are protected by ACCESS_KEYGUARD_SECURE_STORAGE which only system processes can have.
  *
  * @hide
  */
@@ -1284,7 +1315,6 @@
         return getCredentialTypeInternal(userId);
     }
 
-    // TODO: this is a hot path, can we optimize it?
     /**
      * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
      * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
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 7c3ef19..e7bd68e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -225,6 +225,7 @@
         mMediaRouter.rebindAsUser(to.getUserIdentifier());
         synchronized (mLock) {
             if (mProjectionGrant != null) {
+                Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
                 mProjectionGrant.stop();
             }
         }
@@ -260,6 +261,8 @@
         }
 
         synchronized (mLock) {
+            Slog.d(TAG,
+                    "Content Recording: Stopped MediaProjection due to foreground service change");
             if (mProjectionGrant != null) {
                 mProjectionGrant.stop();
             }
@@ -268,6 +271,8 @@
 
     private void startProjectionLocked(final MediaProjection projection) {
         if (mProjectionGrant != null) {
+            Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+                    + "incoming projection");
             mProjectionGrant.stop();
         }
         if (mMediaRouteInfo != null) {
@@ -279,6 +284,8 @@
     }
 
     private void stopProjectionLocked(final MediaProjection projection) {
+        Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+                + "dispatching stop to callbacks");
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
@@ -351,6 +358,13 @@
             if (!setSessionSucceeded) {
                 // Unable to start mirroring, so tear down this projection.
                 if (mProjectionGrant != null) {
+                    String projectionType = incomingSession != null
+                            ? ContentRecordingSession.recordContentToString(
+                                    incomingSession.getContentToRecord()) : "none";
+                    Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+                            + "ContentRecordingSession - id= "
+                            + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
                     mProjectionGrant.stop();
                 }
                 return false;
@@ -464,6 +478,9 @@
                     // The grant may now be null if setting the session failed.
                     if (mProjectionGrant != null) {
                         // Always stop the projection.
+                        Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+                                + "consent result of CANCEL - "
+                                + "id= " + mProjectionGrant.getVirtualDisplayId());
                         mProjectionGrant.stop();
                     }
                     break;
@@ -666,6 +683,7 @@
             try {
                 synchronized (mLock) {
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopping active projection");
                         mProjectionGrant.stop();
                     }
                 }
@@ -864,6 +882,10 @@
                     MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
         }
 
+        int getVirtualDisplayId() {
+            return mVirtualDisplayId;
+        }
+
         @Override // Binder call
         public boolean canProjectVideo() {
             return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -937,12 +959,11 @@
                 registerCallback(mCallback);
                 try {
                     mToken = callback.asBinder();
-                    mDeathEater = new IBinder.DeathRecipient() {
-                        @Override
-                        public void binderDied() {
-                            mCallbackDelegate.remove(callback);
-                            stop();
-                        }
+                    mDeathEater = () -> {
+                        Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+                                + "id= " + mVirtualDisplayId);
+                        mCallbackDelegate.remove(callback);
+                        stop();
                     };
                     mToken.linkToDeath(mDeathEater, 0);
                 } catch (RemoteException e) {
@@ -1012,6 +1033,9 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+                Slog.d(TAG, "Content Recording: handling stopping this projection token"
+                        + " createTime= " + mCreateTimeMs
+                        + " countStarts= " + mCountStarts);
                 stopProjectionLocked(this);
                 mToken.unlinkToDeath(mDeathEater, 0);
                 mToken = null;
@@ -1125,6 +1149,8 @@
                 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
                     mMediaRouteInfo = info;
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+                                + "route type of REMOTE_DISPLAY not selected");
                         mProjectionGrant.stop();
                     }
                 }
@@ -1296,7 +1322,7 @@
             try {
                 mCallback.onStart(mInfo);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify media projection has stopped", e);
+                Slog.w(TAG, "Failed to notify media projection has started", e);
             }
         }
     }
@@ -1370,7 +1396,8 @@
                 return "TYPE_MIRRORING";
             case MediaProjectionManager.TYPE_PRESENTATION:
                 return "TYPE_PRESENTATION";
+            default:
+                return Integer.toString(type);
         }
-        return Integer.toString(type);
     }
 }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 39cd888..8bd2982 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -1050,7 +1050,7 @@
                 context.unregisterReceiver(this);
                 artManager.scheduleBackgroundDexoptJob();
             }
-        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+        }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e73eced..11660a59 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -379,7 +379,12 @@
                 filter.addDataScheme("package");
                 mContext.registerReceiverAsUser(mPackageRemovedListener, UserHandle.ALL, filter,
                         /* broadcastPermission= */ null, mCallbackHandler);
-                mPackageMonitor.register(mContext, UserHandle.ALL, mCallbackHandler);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    mPackageMonitor.register(mContext, UserHandle.ALL, mCallbackHandler);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
                 mIsWatchingPackageBroadcasts = true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dc42644..6bcfcfe 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6251,7 +6251,11 @@
         @Override
         public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
             int uid = Binder.getCallingUid();
-            mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId, uid);
+            int targetUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), uid,
+                    userId, true, true, "registerPackageMonitorCallback",
+                    mContext.getPackageName());
+            mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, targetUserId,
+                    uid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ac52f9f..385dfcb8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4882,6 +4882,7 @@
                     UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
         }
 
+        final boolean isMainUser = (flags & UserInfo.FLAG_MAIN) != 0;
         final boolean isProfile = userTypeDetails.isProfile();
         final boolean isGuest = UserManager.isUserTypeGuest(userType);
         final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
@@ -5028,6 +5029,10 @@
                 }
             } else {
                 userTypeDetails.addDefaultRestrictionsTo(restrictions);
+                if (isMainUser) {
+                    restrictions.remove(UserManager.DISALLOW_OUTGOING_CALLS);
+                    restrictions.remove(UserManager.DISALLOW_SMS);
+                }
             }
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.updateRestrictions(userId, restrictions);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index f7967c0..d5231b5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,7 +295,8 @@
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setCrossProfileIntentFilterAccessControl(
-                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 8dcf3e0..33bed3d 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -171,6 +171,8 @@
     public static class NativeWrapper {
         private native void nativeInit();
 
+        private static native long nativeGetHintSessionPreferredRate();
+
         private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
                 long durationNanos);
 
@@ -190,13 +192,18 @@
 
         private static native void nativeSetThreads(long halPtr, int[] tids);
 
-        private static native long nativeGetHintSessionPreferredRate();
+        private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
 
         /** Wrapper for HintManager.nativeInit */
         public void halInit() {
             nativeInit();
         }
 
+        /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
+        public long halGetHintSessionPreferredRate() {
+            return nativeGetHintSessionPreferredRate();
+        }
+
         /** Wrapper for HintManager.nativeCreateHintSession */
         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
             return nativeCreateHintSession(tgid, uid, tids, durationNanos);
@@ -234,15 +241,16 @@
             nativeSendHint(halPtr, hint);
         }
 
-        /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
-        public long halGetHintSessionPreferredRate() {
-            return nativeGetHintSessionPreferredRate();
-        }
-
         /** Wrapper for HintManager.nativeSetThreads */
         public void halSetThreads(long halPtr, int[] tids) {
             nativeSetThreads(halPtr, tids);
         }
+
+        /** Wrapper for HintManager.setMode */
+        public void halSetMode(long halPtr, int mode, boolean enabled) {
+            nativeSetMode(halPtr, mode, enabled);
+        }
+
     }
 
     @VisibleForTesting
@@ -552,7 +560,7 @@
                 if (mHalSessionPtr == 0 || !updateHintAllowed()) {
                     return;
                 }
-                Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
+                Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
                         + " greater than zero.");
                 mNativeWrapper.halSendHint(mHalSessionPtr, hint);
             }
@@ -593,6 +601,18 @@
             return mThreadIds;
         }
 
+        @Override
+        public void setMode(int mode, boolean enabled) {
+            synchronized (mLock) {
+                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+                    return;
+                }
+                Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
+                        + " greater than zero.");
+                mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
+            }
+        }
+
         private void onProcStateChanged() {
             updateHintAllowed();
         }
diff --git a/services/core/java/com/android/server/power/hint/OWNERS b/services/core/java/com/android/server/power/hint/OWNERS
new file mode 100644
index 0000000..c28c07a
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/OWNERS
@@ -0,0 +1,2 @@
+include /ADPF_OWNERS
+
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
index bc39084..187b939 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
@@ -49,7 +49,7 @@
             + "  Show this message.\n"
             + "dump\n"
             + "  Dump service diagnostics.\n"
-            + "list [--min-version MIN_VERSION]\n"
+            + "list\n"
             + "  List the names of the IRemotelyProvisionedComponent instances.\n"
             + "csr [--challenge CHALLENGE] NAME\n"
             + "  Generate and print a base64-encoded CSR from the named\n"
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index f36ecf7..cb09aef 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -333,7 +333,6 @@
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                 userId);
         List<TvInputInfo> inputList = new ArrayList<>();
-        List<ComponentName> hardwareComponents = new ArrayList<>();
         for (ResolveInfo ri : services) {
             ServiceInfo si = ri.serviceInfo;
             if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
@@ -344,17 +343,16 @@
 
             ComponentName component = new ComponentName(si.packageName, si.name);
             if (hasHardwarePermission(pm, component)) {
-                hardwareComponents.add(component);
                 ServiceState serviceState = userState.serviceStateMap.get(component);
                 if (serviceState == null) {
                     // New hardware input found. Create a new ServiceState and connect to the
                     // service to populate the hardware list.
                     serviceState = new ServiceState(component, userId);
                     userState.serviceStateMap.put(component, serviceState);
-                    updateServiceConnectionLocked(component, userId);
                 } else {
                     inputList.addAll(serviceState.hardwareInputMap.values());
                 }
+                updateServiceConnectionLocked(component, userId);
             } else {
                 try {
                     TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -417,15 +415,6 @@
             }
         }
 
-        // Clean up ServiceState corresponding to the removed hardware inputs
-        Iterator<ServiceState> it = userState.serviceStateMap.values().iterator();
-        while (it.hasNext()) {
-            ServiceState serviceState = it.next();
-            if (serviceState.isHardware && !hardwareComponents.contains(serviceState.component)) {
-                it.remove();
-            }
-        }
-
         userState.inputMap.clear();
         userState.inputMap = inputMap;
     }
@@ -890,10 +879,13 @@
                 sessionState.session = null;
             }
         }
-        logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
-                mCurrentInputId, sessionState);
-        mCurrentInputId = null;
-        mCurrentSessionState = null;
+        if (mCurrentSessionState == sessionState) {
+            // only log when releasing the current on-screen session
+            logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
+                    mCurrentInputId, sessionState);
+            mCurrentInputId = null;
+            mCurrentSessionState = null;
+        }
         removeSessionStateLocked(sessionToken, userId);
         return sessionState;
     }
@@ -3057,6 +3049,7 @@
     }
 
     private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
+        // TODO: handle recording sessions
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(inputId);
         TvInputInfo tvInputInfo = tvInputState.info;
@@ -3443,15 +3436,22 @@
                 synchronized (mLock) {
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                     addHardwareInputLocked(inputInfo);
-                    // catch the use case when a CEC device is unplugged from
-                    // an HDMI port, then plugged in to the same HDMI port.
-                    if (mCurrentInputId != null && mCurrentSessionState != null
-                            && mCurrentInputId.equals(inputInfo.getParentId())
-                            && inputInfo.getId().equals(mCurrentSessionState.inputId)) {
-                        logExternalInputEvent(
-                                FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                inputInfo.getId(), mCurrentSessionState);
-                        mCurrentInputId = inputInfo.getId();
+                    if (mCurrentInputId != null && mCurrentSessionState != null) {
+                        if (TextUtils.equals(mCurrentInputId, inputInfo.getParentId())) {
+                            // catch the use case when a CEC device is plugged in an HDMI port,
+                            // and TV app does not explicitly call tune() to the added CEC input.
+                            logExternalInputEvent(
+                                    FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+                                    inputInfo.getId(), mCurrentSessionState);
+                            mCurrentInputId = inputInfo.getId();
+                        } else if (TextUtils.equals(mCurrentInputId, inputInfo.getId())) {
+                            // catch the use case when a CEC device disconnects itself
+                            // and reconnects to update info.
+                            logExternalInputEvent(
+                                    FrameworkStatsLog
+                                        .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED,
+                                    mCurrentInputId, mCurrentSessionState);
+                        }
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
index 9bf046c..3b930f7 100644
--- a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -36,7 +37,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.function.Consumer;
 import java.util.function.Function;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a7849c1..3125518 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -108,7 +108,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.COLOR_MODE_DEFAULT;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -2791,6 +2790,9 @@
             } else if (isEmbedded()) {
                 associateStartingWindowWithTaskIfNeeded();
             }
+            if (mTransitionController.isCollecting()) {
+                mStartingData.mTransitionId = mTransitionController.getCollectingTransitionId();
+            }
         }
     }
 
@@ -4703,26 +4705,11 @@
     }
 
     /**
-     * @return Whether we are allowed to show non-starting windows at the moment. We disallow
-     *         showing windows while the transition animation is playing in case we have windows
-     *         that have wide-color-gamut color mode set to avoid jank in the middle of the
-     *         animation.
+     * @return Whether we are allowed to show non-starting windows at the moment.
      */
     boolean canShowWindows() {
-        final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+        return mTransitionController.isShellTransitionsEnabled()
                 ? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
-        final boolean animating = mTransitionController.isShellTransitionsEnabled()
-                ? mTransitionController.inPlayingTransition(this)
-                : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
-        return drawn && !(animating && hasNonDefaultColorWindow());
-    }
-
-    /**
-     * @return true if we have a window that has a non-default color mode set; false otherwise.
-     */
-    private boolean hasNonDefaultColorWindow() {
-        return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
-                true /* topToBottom */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 105b2bb..148bf9b 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -16,16 +16,14 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.os.Environment;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -35,7 +33,6 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.server.wm.SnapshotController.TransitionState;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -62,12 +59,6 @@
     static final String SNAPSHOTS_DIRNAME = "activity_snapshots";
 
     /**
-     * The pending activities which should capture snapshot when process transition finish.
-     */
-    @VisibleForTesting
-    final ArraySet<ActivityRecord> mPendingCaptureActivity = new ArraySet<>();
-
-    /**
      * The pending activities which should remove snapshot from memory when process transition
      * finish.
      */
@@ -86,6 +77,10 @@
     @VisibleForTesting
     final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>();
 
+    private final ArraySet<ActivityRecord> mOnBackPressedActivities = new ArraySet<>();
+
+    private final ArrayList<ActivityRecord> mTmpBelowActivities = new ArrayList<>();
+    private final ArrayList<WindowContainer> mTmpTransitionParticipants = new ArrayList<>();
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     private final PersistInfoProvider mPersistInfoProvider;
     private final AppSnapshotLoader mSnapshotLoader;
@@ -117,20 +112,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (shouldDisableSnapshots()) {
-            return;
-        }
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, this::handleOpenActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, this::handleCloseActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, this::handleOpenTaskTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, this::handleCloseTaskTransition);
-    }
-
     @Override
     protected float initSnapshotScale() {
         final float config = mService.mContext.getResources().getFloat(
@@ -173,6 +154,7 @@
 
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles");
                             final File file = mPersistInfoProvider.getDirectory(userId);
                             if (file.exists()) {
                                 final File[] contents = file.listFiles();
@@ -182,15 +164,30 @@
                                     }
                                 }
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
     }
 
+    void addOnBackPressedActivity(ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.add(ar);
+    }
+
+    void clearOnBackPressedActivities() {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.clear();
+    }
+
     /**
-     * Prepare to handle on transition start. Clear all temporary fields.
+     * Prepare to collect any change for snapshots processing. Clear all temporary fields.
      */
-    void preTransitionStart() {
+    void beginSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
@@ -198,18 +195,22 @@
     }
 
     /**
-     * on transition start has notified, start process data.
+     * End collect any change for snapshots processing, start process data.
      */
-    void postTransitionStart() {
+    void endSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
-        onCommitTransition();
+        for (int i = mOnBackPressedActivities.size() - 1; i >= 0; --i) {
+            handleActivityTransition(mOnBackPressedActivities.valueAt(i));
+        }
+        mOnBackPressedActivities.clear();
+        mTmpTransitionParticipants.clear();
+        postProcess();
     }
 
     @VisibleForTesting
     void resetTmpFields() {
-        mPendingCaptureActivity.clear();
         mPendingRemoveActivity.clear();
         mPendingDeleteActivity.clear();
         mPendingLoadActivity.clear();
@@ -218,31 +219,13 @@
     /**
      * Start process all pending activities for a transition.
      */
-    private void onCommitTransition() {
+    private void postProcess() {
         if (DEBUG) {
-            Slog.d(TAG, "ActivitySnapshotController#onCommitTransition result:"
-                    + " capture " + mPendingCaptureActivity
+            Slog.d(TAG, "ActivitySnapshotController#postProcess result:"
                     + " remove " + mPendingRemoveActivity
                     + " delete " + mPendingDeleteActivity
                     + " load " + mPendingLoadActivity);
         }
-        // task snapshots
-        for (int i = mPendingCaptureActivity.size() - 1; i >= 0; i--) {
-            recordSnapshot(mPendingCaptureActivity.valueAt(i));
-        }
-        // clear mTmpRemoveActivity from cache
-        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-        }
-        // clear snapshot on cache and delete files
-        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-            removeIfUserSavedFileExist(code, ar.mUserId);
-        }
         // load snapshot to cache
         for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
             final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
@@ -258,6 +241,8 @@
                             new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                                 @Override
                                 void write() {
+                                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                                            "load_activity_snapshot");
                                     final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code,
                                             userId, false /* loadLowResolutionBitmap */);
                                     synchronized (mService.getWindowManagerLock()) {
@@ -265,16 +250,36 @@
                                             mCache.putSnapshot(ar, snapshot);
                                         }
                                     }
+                                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                                 }
                             });
                 }
             }
         }
+        // clear mTmpRemoveActivity from cache
+        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+        }
+        // clear snapshot on cache and delete files
+        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+            removeIfUserSavedFileExist(code, ar.mUserId);
+        }
         // don't keep any reference
         resetTmpFields();
     }
 
-    private void recordSnapshot(ActivityRecord activity) {
+    void recordSnapshot(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
+        }
         final TaskSnapshot snapshot = recordSnapshotInner(activity, false /* allowSnapshotHome */);
         if (snapshot != null) {
             final int code = getSystemHashCode(activity);
@@ -285,15 +290,20 @@
     /**
      * Called when the visibility of an app changes outside the regular app transition flow.
      */
-    void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
+    void notifyAppVisibilityChanged(ActivityRecord ar, boolean visible) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        final Task task = ar.getTask();
+        if (task == null) {
+            return;
+        }
+        // Doesn't need to capture activity snapshot when it converts from translucent.
         if (!visible) {
             resetTmpFields();
-            addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity,
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, false,
                     "remove-snapshot");
-            onCommitTransition();
+            postProcess();
         }
     }
 
@@ -301,65 +311,146 @@
         return System.identityHashCode(activity);
     }
 
-    void handleOpenActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(false /* open */);
-        for (ActivityRecord ar : participant) {
-            mPendingCaptureActivity.add(ar);
-            // remove the snapshot for the one below close
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingRemoveActivity.add(below);
+    @VisibleForTesting
+    void handleTransitionFinish(@NonNull ArrayList<WindowContainer> windows) {
+        mTmpTransitionParticipants.clear();
+        mTmpTransitionParticipants.addAll(windows);
+        for (int i = mTmpTransitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer next = mTmpTransitionParticipants.get(i);
+            if (next.asTask() != null) {
+                handleTaskTransition(next.asTask());
+            } else if (next.asTaskFragment() != null) {
+                final TaskFragment tf = next.asTaskFragment();
+                final ActivityRecord ar = tf.getTopMostActivity();
+                if (ar != null) {
+                    handleActivityTransition(ar);
+                }
+            } else if (next.asActivityRecord() != null) {
+                handleActivityTransition(next.asActivityRecord());
             }
         }
     }
 
-    void handleCloseActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(true /* open */);
-        for (ActivityRecord ar : participant) {
+    private void handleActivityTransition(@NonNull ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (ar.isVisibleRequested()) {
             mPendingDeleteActivity.add(ar);
             // load next one if exists.
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingLoadActivity.add(below);
-            }
+            addBelowActivityIfExist(ar, mPendingLoadActivity, true, "load-snapshot");
+        } else {
+            // remove the snapshot for the one below close
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    void handleCloseTaskTransition(TransitionState<Task> closeTaskTransitionRecord) {
-        ArraySet<Task> participant = closeTaskTransitionRecord.getParticipant(false /* open */);
-        for (Task close : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(close, mPendingRemoveActivity, "remove-snapshot");
+    private void handleTaskTransition(Task task) {
+        if (shouldDisableSnapshots()) {
+            return;
         }
-    }
-
-    void handleOpenTaskTransition(TransitionState<Task> openTaskTransitionRecord) {
-        ArraySet<Task> participant = openTaskTransitionRecord.getParticipant(true /* open */);
-        for (Task open : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(open, mPendingLoadActivity, "load-snapshot");
+        final ActivityRecord topActivity = task.getTopMostActivity();
+        if (topActivity == null) {
+            return;
+        }
+        if (task.isVisibleRequested()) {
+            // this is open task transition
+            // load the N - 1 to cache
+            addBelowActivityIfExist(topActivity, mPendingLoadActivity, true, "load-snapshot");
             // Move the activities to top of mSavedFilesInOrder, so when purge happen, there
             // will trim the persisted files from the most non-accessed.
-            adjustSavedFileOrder(open);
+            adjustSavedFileOrder(task);
+        } else {
+            // this is close task transition
+            // remove the N - 1 from cache
+            addBelowActivityIfExist(topActivity, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    // Add the top -1 activity to a set if it exists.
-    private void addBelowTopActivityIfExist(Task task, ArraySet<ActivityRecord> set,
-            String debugMessage) {
-        final ActivityRecord topActivity = task.getTopMostActivity();
-        if (topActivity != null) {
-            final ActivityRecord below = task.getActivityBelow(topActivity);
-            if (below != null) {
-                set.add(below);
-                if (DEBUG) {
-                    Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
-                            + below + " from " + debugMessage);
-                }
+    /**
+     * Add the top -1 activity to a set if it exists.
+     * @param inTransition true if the activity must participant in transition.
+     */
+    private void addBelowActivityIfExist(ActivityRecord currentActivity,
+            ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage) {
+        getActivityBelow(currentActivity, inTransition, mTmpBelowActivities);
+        for (int i = mTmpBelowActivities.size() - 1; i >= 0; --i) {
+            set.add(mTmpBelowActivities.get(i));
+            if (DEBUG) {
+                Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
+                        + mTmpBelowActivities.get(i) + " from " + debugMessage);
             }
         }
+        mTmpBelowActivities.clear();
+    }
+
+    private void getActivityBelow(ActivityRecord currentActivity, boolean inTransition,
+            ArrayList<ActivityRecord> result) {
+        final Task currentTask = currentActivity.getTask();
+        if (currentTask == null) {
+            return;
+        }
+        final ActivityRecord initPrev = currentTask.getActivityBelow(currentActivity);
+        if (initPrev == null) {
+            return;
+        }
+        final TaskFragment currTF = currentActivity.getTaskFragment();
+        final TaskFragment prevTF = initPrev.getTaskFragment();
+        final TaskFragment prevAdjacentTF = prevTF != null
+                ? prevTF.getAdjacentTaskFragment() : null;
+        if (currTF == prevTF && currTF != null || prevAdjacentTF == null) {
+            // Current activity and previous one is in the same task fragment, or
+            // previous activity is not in a task fragment, or
+            // previous activity's task fragment doesn't adjacent to any others.
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            return;
+        }
+
+        if (prevAdjacentTF == currTF) {
+            // previous activity A is adjacent to current activity B.
+            // Try to find anyone below previous activityA, which are C and D if exists.
+            // A | B
+            // C (| D)
+            getActivityBelow(initPrev, inTransition, result);
+        } else {
+            // previous activity C isn't adjacent to current activity A.
+            // A
+            // B | C
+            final Task prevAdjacentTask = prevAdjacentTF.getTask();
+            if (prevAdjacentTask == currentTask) {
+                final int currentIndex = currTF != null
+                        ? currentTask.mChildren.indexOf(currTF)
+                        : currentTask.mChildren.indexOf(currentActivity);
+                final int prevAdjacentIndex =
+                        prevAdjacentTask.mChildren.indexOf(prevAdjacentTF);
+                // prevAdjacentTF already above currentActivity
+                if (prevAdjacentIndex > currentIndex) {
+                    return;
+                }
+            }
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            // prevAdjacentTF is adjacent to another one
+            final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+            if (prevAdjacentActivity != null && (!inTransition
+                    || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
+                result.add(prevAdjacentActivity);
+            }
+        }
+    }
+
+    static boolean isInParticipant(ActivityRecord ar,
+            ArrayList<WindowContainer> transitionParticipants) {
+        for (int i = transitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = transitionParticipants.get(i);
+            if (ar == wc || ar.isDescendantOf(wc)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private void adjustSavedFileOrder(Task nextTopTask) {
@@ -376,6 +467,9 @@
 
     @Override
     void onAppRemoved(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppRemoved(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -386,6 +480,9 @@
 
     @Override
     void onAppDied(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppDied(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -440,7 +537,7 @@
     private void removeIfUserSavedFileExist(int code, int userId) {
         final UserSavedFile usf = getUserFiles(userId).get(code);
         if (usf != null) {
-            mUserSavedFiles.remove(code);
+            mUserSavedFiles.get(userId).remove(code);
             mSavedFilesInOrder.remove(usf);
             mPersister.removeSnap(code, userId);
         }
@@ -490,11 +587,13 @@
                     new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activity_remove_files");
                             for (int i = files.size() - 1; i >= 0; --i) {
                                 final UserSavedFile usf = files.get(i);
                                 mSnapshotPersistQueue.deleteSnapshot(
                                         usf.mFileId, usf.mUserId, mPersistInfoProvider);
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8673b90..6c848d1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3029,9 +3029,11 @@
         // Set to activity manager directly to make sure the state can be seen by the subsequent
         // update of scheduling group.
         proc.setRunningAnimationUnsafe();
-        mH.removeMessages(H.UPDATE_PROCESS_ANIMATING_STATE, proc);
-        mH.sendMessageDelayed(mH.obtainMessage(H.UPDATE_PROCESS_ANIMATING_STATE, proc),
+        mH.sendMessage(mH.obtainMessage(H.ADD_WAKEFULNESS_ANIMATING_REASON, proc));
+        mH.removeMessages(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc);
+        mH.sendMessageDelayed(mH.obtainMessage(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc),
                 DOZE_ANIMATING_STATE_RETAIN_TIME_MS);
+        Trace.instant(TRACE_TAG_WINDOW_MANAGER, "requestWakefulnessAnimating");
     }
 
     @Override
@@ -5657,9 +5659,10 @@
 
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
-        static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
         static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
         static final int RESUME_FG_APP_SWITCH_MSG = 4;
+        static final int ADD_WAKEFULNESS_ANIMATING_REASON = 5;
+        static final int REMOVE_WAKEFULNESS_ANIMATING_REASON = 6;
 
         static final int FIRST_ACTIVITY_TASK_MSG = 100;
         static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5676,13 +5679,23 @@
                     tracker.deliverResult(mContext);
                 }
                 break;
-                case UPDATE_PROCESS_ANIMATING_STATE: {
+                case ADD_WAKEFULNESS_ANIMATING_REASON: {
                     final WindowProcessController proc = (WindowProcessController) msg.obj;
                     synchronized (mGlobalLock) {
-                        proc.updateRunningRemoteOrRecentsAnimation();
+                        proc.addAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
                     }
                 }
                 break;
+                case REMOVE_WAKEFULNESS_ANIMATING_REASON: {
+                    final WindowProcessController proc = (WindowProcessController) msg.obj;
+                    synchronized (mGlobalLock) {
+                        proc.removeAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+                    }
+                    Trace.instant(TRACE_TAG_WINDOW_MANAGER, "finishWakefulnessAnimating");
+                }
+                break;
                 case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
                     synchronized (mGlobalLock) {
                         mRetainPowerModeAndTopProcessState = false;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 4ce21bd..2eceecc 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.R;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
@@ -224,7 +225,15 @@
      * operation directly to avoid waiting until timeout.
      */
     void updateTargetWindows() {
-        if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+        if (mTransitionOp == OP_LEGACY) return;
+        if (!mIsStartTransactionCommitted) {
+            if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+                    && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+                Slog.d(TAG, "Cancel for no change");
+                mDisplayContent.finishAsyncRotationIfPossible();
+            }
+            return;
+        }
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final Operation op = mTargetWindowTokens.valueAt(i);
             if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -608,6 +617,16 @@
         return op.mAction != Operation.ACTION_SEAMLESS;
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "AsyncRotationController");
+        prefix += "  ";
+        pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+        pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+        pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+        pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+        pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
+    }
+
     /** The operation to control the rotation appearance associated with window token. */
     private static class Operation {
         @Retention(RetentionPolicy.SOURCE)
@@ -635,5 +654,10 @@
         Operation(@Action int action) {
             mAction = action;
         }
+
+        @Override
+        public String toString() {
+            return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 976641b..993c016 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1177,6 +1177,8 @@
                 if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
                     return null;
                 }
+                mCloseTarget.mTransitionController.mSnapshotController
+                        .mActivitySnapshotController.clearOnBackPressedActivities();
                 applyPreviewStrategy(mOpenAdaptor, openActivity);
 
                 final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
@@ -1222,6 +1224,8 @@
             // Call it again to make sure the activity could be visible while handling the pending
             // animation.
             activity.commitVisibility(true, true);
+            activity.mTransitionController.mSnapshotController
+                    .mActivitySnapshotController.addOnBackPressedActivity(activity);
         }
         activity.mLaunchTaskBehind = true;
 
@@ -1248,6 +1252,9 @@
         // Restore the launch-behind state.
         activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
         activity.mLaunchTaskBehind = false;
+        // Ignore all change
+        activity.mTransitionController.mSnapshotController
+                .mActivitySnapshotController.clearOnBackPressedActivities();
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
                 activity);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 2ecbf8a..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, new RemoteMediaProjectionManagerWrapper());
+        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
     }
 
     @VisibleForTesting
@@ -556,8 +556,14 @@
 
     private static final class RemoteMediaProjectionManagerWrapper implements
             MediaProjectionManagerWrapper {
+
+        private final int mDisplayId;
         @Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
 
+        RemoteMediaProjectionManagerWrapper(int displayId) {
+            mDisplayId = displayId;
+        }
+
         @Override
         public void stopActiveProjection() {
             fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
                 return;
             }
             try {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: stopping active projection for display %d",
+                        mDisplayId);
                 mIMediaProjectionManager.stopActiveProjection();
             } catch (RemoteException e) {
                 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
                         "Content Recording: Unable to tell MediaProjectionManagerService to stop "
-                                + "the active projection: %s",
-                        e);
+                                + "the active projection for display %d: %s",
+                        mDisplayId, e);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
             }
             incomingDisplayContent.setContentRecordingSession(incomingSession);
             // Updating scenario: Explicitly ask ContentRecorder to update, since no config or
-            // display change will trigger an update from the DisplayContent.
-            if (hasSessionUpdatedWithConsent) {
-                incomingDisplayContent.updateRecording();
-            }
+            // display change will trigger an update from the DisplayContent. There exists a
+            // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+            // been set yet due to a race condition. On creation, updateRecording fails to start
+            // recording, so now this call guarantees recording will be started from somewhere.
+            incomingDisplayContent.updateRecording();
         }
         // Takeover and stopping scenario: stop recording on the pre-existing session.
         if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c909cbe..5c82dba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3453,6 +3453,9 @@
                 if (mFixedRotationLaunchingApp != null) {
                     setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
                 }
+            } else if (mAsyncRotationController != null && !isRotationChanging()) {
+                Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+                finishAsyncRotationIfPossible();
             }
             return;
         }
@@ -3626,6 +3629,9 @@
         if (mFixedRotationLaunchingApp != null) {
             pw.println("  mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
         }
+        if (mAsyncRotationController != null) {
+            mAsyncRotationController.dump(pw, prefix);
+        }
 
         pw.println();
         pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 20595ea..73fdfe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,6 +25,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.gui.StalledTransactionInfo;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -96,7 +97,7 @@
     @Override
     public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
-                timeoutMessage("Application does not have a focused window"));
+                timeoutMessage(OptionalInt.empty(), "Application does not have a focused window"));
         mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
     }
 
@@ -104,7 +105,7 @@
     public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
             String reason) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
-                timeoutMessage(reason));
+                timeoutMessage(pid, reason));
         mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
     }
 
@@ -354,11 +355,21 @@
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
 
-    private String timeoutMessage(String reason) {
-        if (reason == null) {
-            return "Input dispatching timed out";
+    private String timeoutMessage(OptionalInt pid, String reason) {
+        String message = (reason == null) ? "Input dispatching timed out."
+                : String.format("Input dispatching timed out (%s).", reason);
+        if (pid.isEmpty()) {
+            return message;
         }
-        return "Input dispatching timed out (" + reason + ")";
+        StalledTransactionInfo stalledTransactionInfo =
+                SurfaceControl.getStalledTransactionInfo(pid.getAsInt());
+        if (stalledTransactionInfo == null) {
+            return message;
+        }
+        return String.format("%s Buffer processing for the associated surface is stuck due to an "
+                + "unsignaled fence (window=%s, bufferId=0x%016X, frameNumber=%s). This "
+                + "potentially indicates a GPU hang.", message, stalledTransactionInfo.layerName,
+                stalledTransactionInfo.bufferId, stalledTransactionInfo.frameNumber);
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3551370..f9fa9e6 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,6 @@
 import static android.view.SurfaceControl.HIDDEN;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -256,11 +255,11 @@
         private final GestureDetector mDoubleTapDetector;
         private final DoubleTapListener mDoubleTapListener;
 
-        TapEventReceiver(InputChannel inputChannel, Context context) {
+        TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
             super(inputChannel, UiThread.getHandler().getLooper());
-            mDoubleTapListener = new DoubleTapListener();
+            mDoubleTapListener = new DoubleTapListener(wmService);
             mDoubleTapDetector = new GestureDetector(
-                    context, mDoubleTapListener, UiThread.getHandler());
+                    wmService.mContext, mDoubleTapListener, UiThread.getHandler());
         }
 
         @Override
@@ -271,14 +270,24 @@
     }
 
     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+        private final WindowManagerService mWmService;
+
+        private DoubleTapListener(WindowManagerService wmService) {
+            mWmService = wmService;
+        }
+
         @Override
         public boolean onDoubleTapEvent(MotionEvent e) {
-            if (e.getAction() == MotionEvent.ACTION_UP) {
-                mDoubleTapCallbackX.accept((int) e.getRawX());
-                mDoubleTapCallbackY.accept((int) e.getRawY());
-                return true;
+            synchronized (mWmService.mGlobalLock) {
+                // This check prevents late events to be handled in case the Letterbox has been
+                // already destroyed and so mOuter.isEmpty() is true.
+                if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
+                    mDoubleTapCallbackX.accept((int) e.getRawX());
+                    mDoubleTapCallbackY.accept((int) e.getRawY());
+                    return true;
+                }
+                return false;
             }
-            return false;
         }
     }
 
@@ -294,7 +303,7 @@
             mWmService = win.mWmService;
             final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
             mClientChannel = mWmService.mInputManager.createInputChannel(name);
-            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);
+            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
 
             mToken = mClientChannel.getToken();
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d3c87a..ba242ec 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -63,6 +63,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -215,6 +217,11 @@
     @Nullable
     private final Boolean mBooleanPropertyAllowForceResizeOverride;
 
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
     /*
      * WindowContainerListener responsible to make translucent activities inherit
      * constraints from the first opaque activity beneath them. It's null for not
@@ -335,6 +342,15 @@
                         /* gatingCondition */ null,
                         PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
 
+        mBooleanPropertyAllowUserAspectRatioOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+        mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
         mIsOverrideToPortraitOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -1109,7 +1125,8 @@
     }
 
     boolean shouldApplyUserMinAspectRatioOverride() {
-        if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
                 || mActivityRecord.mDisplayContent == null
                 || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
@@ -1122,7 +1139,9 @@
     }
 
     boolean shouldApplyUserFullscreenOverride() {
-        if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
                 || mActivityRecord.mDisplayContent == null
                 || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
@@ -1151,7 +1170,8 @@
         }
     }
 
-    private int getUserMinAspectRatioOverrideCode() {
+    @VisibleForTesting
+    int getUserMinAspectRatioOverrideCode() {
         try {
             return mActivityRecord.mAtmService.getPackageManager()
                     .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index badcfa9..37f9730 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -16,185 +16,36 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
-import android.annotation.IntDef;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.util.SparseArray;
+import android.os.Trace;
 import android.view.WindowManager;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.function.Consumer;
 
 /**
  * Integrates common functionality from TaskSnapshotController and ActivitySnapshotController.
  */
 class SnapshotController {
-    private static final boolean DEBUG = false;
-    private static final String TAG = AbsAppSnapshotController.TAG;
-
-    static final int ACTIVITY_OPEN = 1;
-    static final int ACTIVITY_CLOSE = 2;
-    static final int TASK_OPEN = 4;
-    static final int TASK_CLOSE = 8;
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {ACTIVITY_OPEN,
-                    ACTIVITY_CLOSE,
-                    TASK_OPEN,
-                    TASK_CLOSE})
-    @interface TransitionStateType {}
-
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     final TaskSnapshotController mTaskSnapshotController;
     final ActivitySnapshotController mActivitySnapshotController;
 
-    private final ArraySet<Task> mTmpCloseTasks = new ArraySet<>();
-    private final ArraySet<Task> mTmpOpenTasks = new ArraySet<>();
-
-    private final SparseArray<TransitionState> mTmpOpenCloseRecord = new SparseArray<>();
-    private final ArraySet<Integer> mTmpAnalysisRecord = new ArraySet<>();
-    private final SparseArray<ArrayList<Consumer<TransitionState>>> mTransitionStateConsumer =
-            new SparseArray<>();
-    private int mActivatedType;
-
-    private final ActivityOrderCheck mActivityOrderCheck = new ActivityOrderCheck();
-    private final ActivityOrderCheck.AnalysisResult mResultHandler = (type, close, open) -> {
-        addTransitionRecord(type, true/* open */, open);
-        addTransitionRecord(type, false/* open */, close);
-    };
-
-    private static class ActivityOrderCheck {
-        private ActivityRecord mOpenActivity;
-        private ActivityRecord mCloseActivity;
-        private int mOpenIndex = -1;
-        private int mCloseIndex = -1;
-
-        private void reset() {
-            mOpenActivity = null;
-            mCloseActivity = null;
-            mOpenIndex = -1;
-            mCloseIndex = -1;
-        }
-
-        private void setTarget(boolean open, ActivityRecord ar, int index) {
-            if (open) {
-                mOpenActivity = ar;
-                mOpenIndex = index;
-            } else {
-                mCloseActivity = ar;
-                mCloseIndex = index;
-            }
-        }
-
-        void analysisOrder(ArraySet<ActivityRecord> closeApps,
-                ArraySet<ActivityRecord> openApps, Task task, AnalysisResult result) {
-            for (int j = closeApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = closeApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(false, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            for (int j = openApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = openApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(true, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            if (mOpenIndex > mCloseIndex && mCloseIndex != -1) {
-                result.onCheckResult(ACTIVITY_OPEN, mCloseActivity, mOpenActivity);
-            } else if (mOpenIndex < mCloseIndex && mOpenIndex != -1) {
-                result.onCheckResult(ACTIVITY_CLOSE, mCloseActivity, mOpenActivity);
-            }
-            reset();
-        }
-        private interface AnalysisResult {
-            void onCheckResult(@TransitionStateType int type,
-                    ActivityRecord close, ActivityRecord open);
-        }
-    }
-
-    private void addTransitionRecord(int type, boolean open, WindowContainer target) {
-        TransitionState record = mTmpOpenCloseRecord.get(type);
-        if (record == null) {
-            record =  new TransitionState();
-            mTmpOpenCloseRecord.set(type, record);
-        }
-        record.addParticipant(target, open);
-        mTmpAnalysisRecord.add(type);
-    }
-
-    private void clearRecord() {
-        mTmpOpenCloseRecord.clear();
-        mTmpAnalysisRecord.clear();
-    }
-
-    static class TransitionState<TYPE extends WindowContainer> {
-        private final ArraySet<TYPE> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<TYPE> mCloseParticipant = new ArraySet<>();
-
-        void addParticipant(TYPE target, boolean open) {
-            final ArraySet<TYPE> participant = open
-                    ? mOpenParticipant : mCloseParticipant;
-            participant.add(target);
-        }
-
-        ArraySet<TYPE> getParticipant(boolean open) {
-            return open ? mOpenParticipant : mCloseParticipant;
-        }
-    }
-
     SnapshotController(WindowManagerService wms) {
         mSnapshotPersistQueue = new SnapshotPersistQueue();
         mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue);
         mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue);
     }
 
-    void registerTransitionStateConsumer(@TransitionStateType int type,
-            Consumer<TransitionState> consumer) {
-        ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            consumers = new ArrayList<>();
-            mTransitionStateConsumer.set(type, consumers);
-        }
-        if (!consumers.contains(consumer)) {
-            consumers.add(consumer);
-        }
-        mActivatedType |= type;
-    }
-
-    void unregisterTransitionStateConsumer(int type, Consumer<TransitionState> consumer) {
-        final ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            return;
-        }
-        consumers.remove(consumer);
-        if (consumers.size() == 0) {
-            mActivatedType &= ~type;
-        }
-    }
-
-    private boolean hasTransitionStateConsumer(@TransitionStateType int type) {
-        return (mActivatedType & type) != 0;
-    }
-
     void systemReady() {
         mSnapshotPersistQueue.systemReady();
-        mTaskSnapshotController.systemReady();
-        mActivitySnapshotController.systemReady();
     }
 
     void setPause(boolean paused) {
@@ -212,47 +63,69 @@
     }
 
     void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
-        if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) {
-            final Task task = appWindowToken.getTask();
-            if (task == null || task.isVisibleRequested()) {
-                return;
+        mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
+    }
+
+    // For legacy transition, which won't support activity snapshot
+    void onTransitionStarting(DisplayContent displayContent) {
+        mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
+    }
+
+    // For shell transition, record snapshots before transaction start.
+    void onTransactionReady(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
+        final boolean isTransitionOpen = isTransitionOpen(type);
+        final boolean isTransitionClose = isTransitionClose(type);
+        if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
+            return;
+        }
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            Transition.ChangeInfo info = changeInfos.get(i);
+            // Intentionally skip record snapshot for changes originated from PiP.
+            if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
+            if (info.mContainer.asTask() != null && !info.mContainer.isVisibleRequested()) {
+                mTaskSnapshotController.recordSnapshot(info.mContainer.asTask(),
+                        false /* allowSnapshotHome */);
             }
-            // close task transition
-            addTransitionRecord(TASK_CLOSE, false /*open*/, task);
-            mActivitySnapshotController.preTransitionStart();
-            notifyTransition(TASK_CLOSE);
-            mActivitySnapshotController.postTransitionStart();
-            clearRecord();
+            // Won't need to capture activity snapshot in close transition.
+            if (isTransitionClose) {
+                continue;
+            }
+            if (info.mContainer.asActivityRecord() != null
+                    || info.mContainer.asTaskFragment() != null) {
+                final TaskFragment tf = info.mContainer.asTaskFragment();
+                final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
+                        : info.mContainer.asActivityRecord();
+                final boolean taskVis = ar != null && ar.getTask().isVisibleRequested();
+                if (ar != null && !ar.isVisibleRequested() && taskVis) {
+                    mActivitySnapshotController.recordSnapshot(ar);
+                }
+            }
         }
     }
 
-    // For legacy transition
-    void onTransitionStarting(DisplayContent displayContent) {
-        handleAppTransition(displayContent.mClosingApps, displayContent.mOpeningApps);
-    }
-
-    // For shell transition, adapt to legacy transition.
-    void onTransitionReady(@WindowManager.TransitionType int type,
-            ArraySet<WindowContainer> participants) {
+    void onTransitionFinish(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
         final boolean isTransitionOpen = isTransitionOpen(type);
         final boolean isTransitionClose = isTransitionClose(type);
         if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM
-                || (mActivatedType == 0)) {
+                || (changeInfos.isEmpty())) {
             return;
         }
-        final ArraySet<ActivityRecord> openingApps = new ArraySet<>();
-        final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
-
-        for (int i = participants.size() - 1; i >= 0; --i) {
-            final ActivityRecord ar = participants.valueAt(i).asActivityRecord();
-            if (ar == null || ar.getTask() == null) continue;
-            if (ar.isVisibleRequested()) {
-                openingApps.add(ar);
-            } else {
-                closingApps.add(ar);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SnapshotController_analysis");
+        mActivitySnapshotController.beginSnapshotProcess();
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = changeInfos.get(i).mContainer;
+            if (wc.asTask() == null && wc.asTaskFragment() == null
+                    && wc.asActivityRecord() == null) {
+                continue;
             }
+            windows.add(wc);
         }
-        handleAppTransition(closingApps, openingApps);
+        mActivitySnapshotController.handleTransitionFinish(windows);
+        mActivitySnapshotController.endSnapshotProcess();
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     private static boolean isTransitionOpen(int type) {
@@ -262,78 +135,6 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
-    @VisibleForTesting
-    void handleAppTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openApps) {
-        if (mActivatedType == 0) {
-            return;
-        }
-        analysisTransition(closingApps, openApps);
-        mActivitySnapshotController.preTransitionStart();
-        for (Integer transitionType : mTmpAnalysisRecord) {
-            notifyTransition(transitionType);
-        }
-        mActivitySnapshotController.postTransitionStart();
-        clearRecord();
-    }
-
-    private void notifyTransition(int transitionType) {
-        final TransitionState record = mTmpOpenCloseRecord.get(transitionType);
-        final ArrayList<Consumer<TransitionState>> consumers =
-                mTransitionStateConsumer.get(transitionType);
-        for (Consumer<TransitionState> consumer : consumers) {
-            consumer.accept(record);
-        }
-    }
-
-    private void analysisTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openingApps) {
-        getParticipantTasks(closingApps, mTmpCloseTasks, false /* isOpen */);
-        getParticipantTasks(openingApps, mTmpOpenTasks, true /* isOpen */);
-        if (DEBUG) {
-            Slog.d(TAG, "AppSnapshotController#analysisTransition participants"
-                    + " mTmpCloseTasks " + mTmpCloseTasks
-                    + " mTmpOpenTasks " + mTmpOpenTasks);
-        }
-        for (int i = mTmpCloseTasks.size() - 1; i >= 0; i--) {
-            final Task closeTask = mTmpCloseTasks.valueAt(i);
-            if (mTmpOpenTasks.contains(closeTask)) {
-                if (hasTransitionStateConsumer(ACTIVITY_OPEN)
-                        || hasTransitionStateConsumer(ACTIVITY_CLOSE)) {
-                    mActivityOrderCheck.analysisOrder(closingApps, openingApps, closeTask,
-                            mResultHandler);
-                }
-            } else if (hasTransitionStateConsumer(TASK_CLOSE)) {
-                // close task transition
-                addTransitionRecord(TASK_CLOSE, false /*open*/, closeTask);
-            }
-        }
-        if (hasTransitionStateConsumer(TASK_OPEN)) {
-            for (int i = mTmpOpenTasks.size() - 1; i >= 0; i--) {
-                final Task openTask = mTmpOpenTasks.valueAt(i);
-                if (!mTmpCloseTasks.contains(openTask)) {
-                    // this is open task transition
-                    addTransitionRecord(TASK_OPEN, true /*open*/, openTask);
-                }
-            }
-        }
-        mTmpCloseTasks.clear();
-        mTmpOpenTasks.clear();
-    }
-
-    private void getParticipantTasks(ArraySet<ActivityRecord> activityRecords, ArraySet<Task> tasks,
-            boolean isOpen) {
-        for (int i = activityRecords.size() - 1; i >= 0; i--) {
-            final ActivityRecord activity = activityRecords.valueAt(i);
-            final Task task = activity.getTask();
-            if (task == null) continue;
-
-            if (isOpen == activity.isVisibleRequested()) {
-                tasks.add(task);
-            }
-        }
-    }
-
     void dump(PrintWriter pw, String prefix) {
         mTaskSnapshotController.dump(pw, prefix);
         mActivitySnapshotController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 58e1c54..f4f641f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.graphics.Bitmap.CompressFormat.JPEG;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -26,6 +27,7 @@
 import android.graphics.Bitmap;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.window.TaskSnapshot;
@@ -249,6 +251,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem");
             if (!mPersistInfoProvider.createDirectory(mUserId)) {
                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
                         + mPersistInfoProvider.getDirectory(mUserId));
@@ -263,6 +266,7 @@
             if (failed) {
                 deleteSnapshot(mId, mUserId, mPersistInfoProvider);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         boolean writeProto() {
@@ -373,7 +377,9 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem");
             deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 2b22d75..34806bd 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -65,6 +65,9 @@
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
 
+    /** Non-zero if this starting window is added in a collecting transition. */
+    int mTransitionId;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4e95c84..69eddb9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2878,8 +2878,8 @@
             // No need to check if allowed if it's leaving dragResize
             if (dragResizing
                     && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
-                throw new IllegalArgumentException("Drag resize not allow for root task id="
-                        + getRootTaskId());
+                Slog.e(TAG, "Drag resize isn't allowed for root task id=" + getRootTaskId());
+                return;
             }
             mDragResizing = dragResizing;
             resetDragResizingChangeReported();
@@ -5737,7 +5737,7 @@
             }
         }
         ActivityRecord topActivity = getDisplayArea().topRunningActivity();
-        Task topRootTask = topActivity.getRootTask();
+        Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
         if (topRootTask != null && topRootTask != this && topActivity.isState(RESUMED)) {
             // Usually resuming a top activity triggers the next app transition, but nothing's got
             // resumed in this case, so we need to execute it explicitly.
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c747c09..4eb4290 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -77,13 +76,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (!shouldDisableSnapshots()) {
-            mService.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                    this::handleTaskClose);
-        }
-    }
-
     static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
             BaseAppSnapshotPersister.DirectoryResolver resolver) {
         final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -116,20 +108,23 @@
                 enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
     }
 
-    void handleTaskClose(SnapshotController.TransitionState<Task> closeTaskTransitionRecord) {
+    // Still needed for legacy transition.(AppTransitionControllerTest)
+    void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        // We need to take a snapshot of the task if and only if all activities of the task are
+        // either closing or hidden.
         mTmpTasks.clear();
-        final ArraySet<Task> tasks = closeTaskTransitionRecord.getParticipant(false /* open */);
-        if (mService.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
-            mTmpTasks.addAll(tasks);
-        } else {
-            for (Task task : tasks) {
-                getClosingTasksInner(task, mTmpTasks);
-            }
+        for (int i = closingApps.size() - 1; i >= 0; i--) {
+            final ActivityRecord activity = closingApps.valueAt(i);
+            final Task task = activity.getTask();
+            if (task == null) continue;
+
+            getClosingTasksInner(task, mTmpTasks);
         }
         snapshotTasks(mTmpTasks);
+        mTmpTasks.clear();
         mSkipClosingAppSnapshotTasks.clear();
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index cd15119..3e8c017 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.os.Trace;
 import android.util.ArraySet;
 import android.window.TaskSnapshot;
 
@@ -102,6 +105,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
             final ArraySet<Integer> newPersistedTaskIds;
             synchronized (mLock) {
                 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
@@ -120,6 +124,7 @@
                     }
                 }
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index aad17aa..1566bb2c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1191,8 +1191,6 @@
                                         "  Skipping post-transition snapshot for task %d",
                                         task.mTaskId);
                             }
-                            snapController.mActivitySnapshotController
-                                    .notifyAppVisibilityChanged(ar, false /* visible */);
                         }
                         ar.commitVisibility(false /* visible */, false /* performLayout */,
                                 true /* fromTransition */);
@@ -1389,6 +1387,7 @@
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
         mController.mFinishingTransition = null;
+        mController.mSnapshotController.onTransitionFinish(mType, mTargets);
     }
 
     void abort() {
@@ -1593,16 +1592,7 @@
         // transferred. If transition is transient, IME won't be moved during the transition and
         // the tasks are still live, so we take the snapshot at the end of the transition instead.
         if (mTransientLaunches == null) {
-            for (int i = mParticipants.size() - 1; i >= 0; --i) {
-                final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-                if (ar == null || ar.getTask() == null
-                        || ar.getTask().isVisibleRequested()) continue;
-                final ChangeInfo change = mChanges.get(ar);
-                // Intentionally skip record snapshot for changes originated from PiP.
-                if (change != null && change.mWindowingMode == WINDOWING_MODE_PINNED) continue;
-                mController.mSnapshotController.mTaskSnapshotController.recordSnapshot(
-                        ar.getTask(), false /* allowSnapshotHome */);
-            }
+            mController.mSnapshotController.onTransactionReady(mType, mTargets);
         }
 
         // This is non-null only if display has changes. It handles the visible windows that don't
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index af8fb02..c59d2d3 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -145,6 +145,27 @@
         }
     }
 
+    void logRemovingStartingWindow(@NonNull StartingData startingData) {
+        if (startingData.mTransitionId == 0) {
+            return;
+        }
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    startingData.mTransitionId);
+            outputStream.write(
+                    com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
     private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
             Transition transition, ArrayList<ChangeInfo> targets) {
         Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6630e20..c7fd147 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -156,7 +156,7 @@
                 linkFixedRotationTransform(wallpaperTarget.mToken);
             }
         }
-        if (mTransitionController.isShellTransitionsEnabled()) {
+        if (mTransitionController.inTransition(this)) {
             // If wallpaper is in transition, setVisible() will be called from commitVisibility()
             // when finishing transition. Otherwise commitVisibility() is already called from above
             // setVisibility().
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 427ab7e..6d7e297 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1657,9 +1657,18 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
-        final TaskFragment root2 =
-                WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
+        final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
+        if (wc1 == null || !wc1.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root1 = wc1.asTaskFragment();
+        final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
+        if (wc2 == null || !wc2.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root2 = wc2.asTaskFragment();
         if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
@@ -1672,7 +1681,12 @@
     }
 
     private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+        if (wc == null || !wc.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root = wc.asTaskFragment();
         if (!root.mCreatedByOrganizer) {
             throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root=" + root);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 83e8646..e769a27 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -48,6 +48,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -76,7 +77,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -88,6 +88,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -250,11 +252,30 @@
     @Nullable
     private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
 
-    /** Whether our process is currently running a {@link RecentsAnimation} */
-    private boolean mRunningRecentsAnimation;
+    /**
+     * It can be set for a running transition player ({@link android.window.ITransitionPlayer}) or
+     * remote animators (running {@link android.window.IRemoteTransition}).
+     */
+    static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
+    /** It is set for wakefulness transition. */
+    static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
+    /** Whether the legacy {@link RecentsAnimation} is running. */
+    static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
 
-    /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
-    private boolean mRunningRemoteAnimation;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            ANIMATING_REASON_REMOTE_ANIMATION,
+            ANIMATING_REASON_WAKEFULNESS_CHANGE,
+            ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
+    })
+    @interface AnimatingReason {}
+
+    /**
+     * Non-zero if this process is currently running an important animation. This should be never
+     * set for system server.
+     */
+    @AnimatingReason
+    private int mAnimatingReasons;
 
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
@@ -1869,30 +1890,45 @@
     }
 
     void setRunningRecentsAnimation(boolean running) {
-        if (mRunningRecentsAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
         }
-        mRunningRecentsAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
     void setRunningRemoteAnimation(boolean running) {
-        if (mRunningRemoteAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
         }
-        mRunningRemoteAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
-    void updateRunningRemoteOrRecentsAnimation() {
+    void addAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons |= reason;
+        if (prevReasons == 0) {
+            setAnimating(true);
+        }
+    }
+
+    void removeAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons &= ~reason;
+        if (prevReasons != 0 && mAnimatingReasons == 0) {
+            setAnimating(false);
+        }
+    }
+
+    /** Applies the animating state to activity manager for updating process priority. */
+    private void setAnimating(boolean animating) {
         // Posting on handler so WM lock isn't held when we call into AM.
-        mAtm.mH.sendMessage(PooledLambda.obtainMessage(
-                WindowProcessListener::setRunningRemoteAnimation, mListener,
-                isRunningRemoteTransition()));
+        mAtm.mH.post(() -> mListener.setRunningRemoteAnimation(animating));
     }
 
     boolean isRunningRemoteTransition() {
-        return mRunningRecentsAnimation || mRunningRemoteAnimation;
+        return (mAnimatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0;
     }
 
     /** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
@@ -1946,6 +1982,21 @@
         pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
                 ? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
 
+        final int animatingReasons = mAnimatingReasons;
+        if (animatingReasons != 0) {
+            pw.print(prefix + " mAnimatingReasons=");
+            if ((animatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0) {
+                pw.print("remote-animation|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
+                pw.print("wakefulness|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
+                pw.print("legacy-recents");
+            }
+            pw.println();
+        }
+
         final int stateFlags = mActivityStateFlags;
         if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
             pw.print(prefix + " mActivityStateFlags=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 029f46f..5a45fe1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2334,6 +2334,8 @@
             mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
             // Fix the starting window to task when Activity has changed.
             if (mStartingData != null && mStartingData.mAssociatedTask == null
+                    && mTempConfiguration.windowConfiguration.getRotation()
+                            == selfConfiguration.windowConfiguration.getRotation()
                     && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
                 mStartingData.mResizedFromTransfer = true;
                 // Lock the starting window to task, so it won't resize from transfer anymore.
@@ -2410,7 +2412,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
 
-        final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
+        final boolean startingWindow = mStartingData != null;
         if (startingWindow) {
             ProtoLog.d(WM_DEBUG_STARTING_WINDOW, "Starting window removed %s", this);
             // Cancel the remove starting window animation on shell. The main window might changed
@@ -2424,6 +2426,7 @@
                     return false;
                 }, true);
             }
+            mTransitionController.mTransitionTracer.logRemovingStartingWindow(mStartingData);
         } else if (mAttrs.type == TYPE_BASE_APPLICATION
                 && isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
             // Cancel the remove starting window animation in case the binder dead before remove
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 101af4d..405b133 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -94,7 +94,10 @@
 
 cc_defaults {
     name: "libservices.core-libs",
-    defaults: ["android.hardware.graphics.common-ndk_shared"],
+    defaults: [
+        "android.hardware.graphics.common-ndk_shared",
+        "android.hardware.power-ndk_shared",
+    ],
     shared_libs: [
         "libadb_pairing_server",
         "libadb_pairing_connection",
@@ -177,7 +180,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-ndk",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4343edd..cfc63f0 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -233,6 +233,7 @@
 // process_madvise on failure
 int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
                          uint64_t* outBytesProcessed) {
+    static const size_t kPageSize = getpagesize();
     if (batch.totalVmas == 0 || batch.totalBytes == 0) {
         // No VMAs in Batch, skip.
         *outBytesProcessed = 0;
@@ -258,7 +259,7 @@
     } else if (bytesProcessedInSend < batch.totalBytes) {
         // Partially processed the bytes requested
         // skip last page which is where it failed.
-        bytesProcessedInSend += PAGE_SIZE;
+        bytesProcessedInSend += kPageSize;
     }
     bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
 
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index ad098b7..4cd018b 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -144,12 +144,20 @@
             }
             uinput_abs_setup slotAbsSetup;
             slotAbsSetup.code = ABS_MT_SLOT;
-            slotAbsSetup.absinfo.maximum = MAX_POINTERS;
+            slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
             slotAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno));
                 return invalidFd();
             }
+            uinput_abs_setup trackingIdAbsSetup;
+            trackingIdAbsSetup.code = ABS_MT_TRACKING_ID;
+            trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
+            trackingIdAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
+                return invalidFd();
+            }
         }
         if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index e148b94..7edf445 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -31,6 +31,7 @@
 
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
 using aidl::android::hardware::power::WorkDuration;
 
 using android::base::StringPrintf;
@@ -41,6 +42,15 @@
 static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
 static std::mutex gSessionMapLock;
 
+static int64_t getHintSessionPreferredRate() {
+    int64_t rate = -1;
+    auto result = gPowerHalController.getHintSessionPreferredRate();
+    if (result.isOk()) {
+        rate = result.value();
+    }
+    return rate;
+}
+
 static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
                                std::vector<int32_t> threadIds, int64_t durationNanos) {
     auto result = gPowerHalController.createHintSession(tgid, uid, threadIds, durationNanos);
@@ -93,13 +103,9 @@
     appSession->setThreads(threadIds);
 }
 
-static int64_t getHintSessionPreferredRate() {
-    int64_t rate = -1;
-    auto result = gPowerHalController.getHintSessionPreferredRate();
-    if (result.isOk()) {
-        rate = result.value();
-    }
-    return rate;
+static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    appSession->setMode(mode, enabled);
 }
 
 // ----------------------------------------------------------------------------
@@ -107,6 +113,10 @@
     gPowerHalController.init();
 }
 
+static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(getHintSessionPreferredRate());
+}
+
 static jlong nativeCreateHintSession(JNIEnv* env, jclass /* clazz */, jint tgid, jint uid,
                                      jintArray tids, jlong durationNanos) {
     ScopedIntArrayRO tidArray(env, tids);
@@ -165,14 +175,16 @@
     setThreads(session_ptr, threadIds);
 }
 
-static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
-    return static_cast<jlong>(getHintSessionPreferredRate());
+static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode,
+                          jboolean enabled) {
+    setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
 }
 
 // ----------------------------------------------------------------------------
 static const JNINativeMethod sHintManagerServiceMethods[] = {
         /* name, signature, funcPtr */
         {"nativeInit", "()V", (void*)nativeInit},
+        {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
         {"nativeCreateHintSession", "(II[IJ)J", (void*)nativeCreateHintSession},
         {"nativePauseHintSession", "(J)V", (void*)nativePauseHintSession},
         {"nativeResumeHintSession", "(J)V", (void*)nativeResumeHintSession},
@@ -181,7 +193,7 @@
         {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
-        {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
+        {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
 };
 
 int register_android_server_HintManagerService(JNIEnv* env) {
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index b620407..f5360eb 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -30,6 +30,7 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.pm.ShortcutInfo;
@@ -39,6 +40,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -63,6 +65,7 @@
     private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
     private final IntentFilter mIntentFilter;
     private final AppPredictor mRemoteAppPredictor;
+    @Nullable private final String mChooserActivity;
 
     ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
@@ -81,6 +84,9 @@
         } else {
             mRemoteAppPredictor = null;
         }
+        ComponentName component = ComponentName.unflattenFromString(
+                context.getResources().getString(R.string.config_chooserActivity));
+        mChooserActivity = (component == null) ? null : component.getShortClassName();
     }
 
     /** Reports chosen history of direct/app share targets. */
@@ -138,7 +144,7 @@
         SharesheetModelScorer.computeScoreForAppShare(shareTargets,
                 getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
                 System.currentTimeMillis(), getDataManager(),
-                mCallingUserId);
+                mCallingUserId, mChooserActivity);
         Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
         List<AppTarget> appTargetList = new ArrayList<>();
         for (ShareTarget shareTarget : shareTargets) {
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..b2f1e21 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -26,7 +26,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
 import com.android.server.people.data.AppUsageStatsData;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.Event;
@@ -55,8 +54,6 @@
     private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
     @VisibleForTesting
     static final float FOREGROUND_APP_WEIGHT = 0F;
-    @VisibleForTesting
-    static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
 
     // Keep constructor private to avoid class being instantiated.
     private SharesheetModelScorer() {
@@ -169,13 +166,14 @@
      */
     static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
             int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
-            @UserIdInt int callingUserId) {
+            @UserIdInt int callingUserId, @Nullable String chooserActivity) {
         computeScore(shareTargets, shareEventType, now);
-        postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+        postProcess(shareTargets, targetsLimit, dataManager, callingUserId, chooserActivity);
     }
 
     private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
-            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         // Populates a map which key is package name and value is list of shareTargets descended
         // on total score.
         Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
@@ -192,7 +190,7 @@
             }
             targetsList.add(index, shareTarget);
         }
-        promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+        promoteForegroundApp(shareTargetMap, dataManager, callingUserId, chooserActivity);
         promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
                 callingUserId);
     }
@@ -272,9 +270,10 @@
      */
     private static void promoteForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
-                callingUserId);
+                callingUserId, chooserActivity);
         if (sharingForegroundApp != null) {
             ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
                     0);
@@ -297,7 +296,8 @@
     @Nullable
     private static String findSharingForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = null;
         long now = System.currentTimeMillis();
         List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
@@ -306,8 +306,8 @@
         for (int i = events.size() - 1; i >= 0; i--) {
             String className = events.get(i).getClassName();
             String packageName = events.get(i).getPackageName();
-            if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
-                    || packageName.contains(CHOOSER_ACTIVITY)) {
+            if (packageName == null || (className != null && chooserActivity != null
+                    && className.contains(chooserActivity))) {
                 continue;
             }
             if (sourceApp == null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
new file mode 100644
index 0000000..0ff4724
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BrightnessLowPowerModeModifierTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+    private static final float DEFAULT_BRIGHTNESS = 0.5f;
+    private static final float LOW_POWER_BRIGHTNESS_FACTOR = 0.8f;
+    private static final float EXPECTED_LOW_POWER_BRIGHTNESS =
+            DEFAULT_BRIGHTNESS * LOW_POWER_BRIGHTNESS_FACTOR;
+    private final DisplayPowerRequest mRequest = new DisplayPowerRequest();
+    private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+    private BrightnessLowPowerModeModifier mModifier;
+
+    @Before
+    public void setUp() {
+        mModifier = new BrightnessLowPowerModeModifier();
+        mRequest.screenLowPowerBrightnessFactor = LOW_POWER_BRIGHTNESS_FACTOR;
+        mRequest.lowPowerMode = true;
+    }
+
+    @Test
+    public void testApply_lowPowerModeOff() {
+        mRequest.lowPowerMode = false;
+
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertTrue(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOn() {
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerBrightnessFactorHigh() {
+        mRequest.screenLowPowerBrightnessFactor = 1.1f;
+
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndMinBrightness() {
+        mBuilder.setBrightness(0.0f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerAlreadyApplied() {
+        mModifier.apply(mRequest, mBuilder);
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                builder.getBrightnessReason().getModifier());
+        assertTrue(builder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOffAfterLowPowerOn() {
+        mModifier.apply(mRequest, mBuilder);
+        mRequest.lowPowerMode = false;
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, builder.getBrightnessReason().getModifier());
+        assertFalse(builder.isSlowChange());
+    }
+
+    private DisplayBrightnessState.Builder prepareBuilder() {
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setBrightness(DEFAULT_BRIGHTNESS);
+        builder.setIsSlowChange(true);
+        return builder;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
new file mode 100644
index 0000000..be4e7c7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class DisplayDimModifierTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+    private static final float DEFAULT_BRIGHTNESS = 0.5f;
+    private static final float MIN_DIM_AMOUNT = 0.05f;
+    private static final float DIM_CONFIG = 0.4f;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private PowerManager mMockPowerManager;
+
+    @Mock
+    private Resources mMockResources;
+
+    private final DisplayManagerInternal.DisplayPowerRequest
+            mRequest = new DisplayManagerInternal.DisplayPowerRequest();
+    private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+    private DisplayDimModifier mModifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
+        when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+        when(mMockPowerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+
+        mModifier = new DisplayDimModifier(mMockContext);
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+    }
+
+    @Test
+    public void testApply_noDimPolicy() {
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertTrue(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyFromResources() {
+        mBuilder.setBrightness(0.4f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.4f - MIN_DIM_AMOUNT, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyFromConfig() {
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DIM_CONFIG, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyAndDimPolicyAlreadyApplied() {
+        mModifier.apply(mRequest, mBuilder);
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DIM_CONFIG, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                builder.getBrightnessReason().getModifier());
+        assertTrue(builder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyAndMinBrightness() {
+        mBuilder.setBrightness(0.0f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyOffAfterDimPolicyOn() {
+        mModifier.apply(mRequest, mBuilder);
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, builder.getBrightnessReason().getModifier());
+        assertFalse(builder.isSlowChange());
+    }
+
+    private DisplayBrightnessState.Builder prepareBuilder() {
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setBrightness(DEFAULT_BRIGHTNESS);
+        builder.setIsSlowChange(true);
+        return builder;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index c6d8848..4095be7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -83,7 +83,8 @@
                         "slices",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -106,7 +107,8 @@
                         "account_manager",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -121,7 +123,8 @@
                         "account_sync_settings",
                         "notifications",
                         "permissions",
-                        "app_locales");
+                        "app_locales",
+                        "companion");
     }
 
     @Test
@@ -140,7 +143,8 @@
                         "app_locales",
                         "account_manager",
                         "usage_stats",
-                        "shortcut_manager");
+                        "shortcut_manager",
+                        "companion");
     }
 
     private class TestableSystemBackupAgent extends SystemBackupAgent {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 0664ab8..d32b6be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,7 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
+import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -490,6 +493,17 @@
         assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
     }
 
+    @Test
+    public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+        UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
+                UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
+
+        assertThat(mUms.hasUserRestriction(DISALLOW_OUTGOING_CALLS, mainUser.id))
+                .isFalse();
+        assertThat(mUms.hasUserRestriction(DISALLOW_SMS, mainUser.id))
+                .isFalse();
+    }
+
     private void resetUserSwitcherEnabled() {
         mUms.putUserInfo(new UserInfo(USER_ID, "Test User", 0));
         mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 19fb2c9..0a8c570 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -51,7 +51,6 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.platform.test.annotations.FlakyTest;
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
@@ -59,6 +58,7 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ConcurrentUtils;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 0cfddd3..769be17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -351,6 +351,8 @@
         assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
                         : BiometricSensor.STATE_COOKIE_RETURNED,
                 session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+        verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+                eq(session.isCrypto()));
 
         // start fingerprint sensor if it was delayed
         if (!startFingerprintNow) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..99d66c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AuthenticationStatsCollectorTest {
+
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    private static final float FRR_THRESHOLD = 0.2f;
+    private static final int USER_ID_1 = 1;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                0 /* modality */);
+    }
+
+
+    @Test
+    public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+        assertEquals(USER_ID_1, authenticationStats.getUserId());
+        assertEquals(1, authenticationStats.getTotalAttempts());
+        assertEquals(0, authenticationStats.getRejectedAttempts());
+        assertEquals(0, authenticationStats.getEnrollmentNotifications());
+    }
+
+    @Test
+    public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+        assertEquals(USER_ID_1, authenticationStats.getUserId());
+        assertEquals(1, authenticationStats.getTotalAttempts());
+        assertEquals(1, authenticationStats.getRejectedAttempts());
+        assertEquals(0, authenticationStats.getEnrollmentNotifications());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+    @Test
+    public void authenticate_statsShouldBeUpdated() {
+        AuthenticationStats authenticationStats =
+                new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+                        0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */);
+
+        authenticationStats.authenticate(true /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 1);
+        assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+        authenticationStats.authenticate(false /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 2);
+        assertEquals(authenticationStats.getRejectedAttempts(), 1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
 import org.junit.Before;
@@ -65,6 +66,8 @@
     @Mock
     private BiometricFrameworkStatsLogger mSink;
     @Mock
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    @Mock
     private SensorManager mSensorManager;
     @Mock
     private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
     }
 
     private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
-        return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+        return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+                mAuthenticationStatsCollector, mSensorManager);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index d1d6e9d..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
@@ -39,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -59,11 +61,15 @@
 
     private static final String TAG = "FaceProviderTest";
 
+    private static final float FRR_THRESHOLD = 0.2f;
+
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private IFace mDaemon;
     @Mock
     private BiometricContext mBiometricContext;
@@ -86,6 +92,10 @@
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
         when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
         sensor1.commonProps.sensorId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index d174533..e558c4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
     private static final String TAG = "Face10Test";
     private static final int SENSOR_ID = 1;
     private static final int USER_ID = 20;
+    private static final float FRR_THRESHOLD = 0.2f;
 
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private BiometricScheduler mScheduler;
     @Mock
     private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         final int maxEnrollmentsPerUser = 1;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index ffe088c..a621055 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -19,14 +19,17 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -307,4 +310,60 @@
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
                         HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
     }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcSupported_writesAtom() {
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcNotSupported_writesAtom() {
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcNotSupported_writesAtom_logReasonWake() {
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 55e5dbd..c632727f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -174,6 +174,20 @@
                     protected boolean earcBlocksArcConnection() {
                         return mEarcBlocksArc;
                     }
+
+                    /**
+                     * Override displayOsd to prevent it from broadcasting an intent, which
+                     * can trigger a SecurityException.
+                    */
+                    @Override
+                    void displayOsd(int messageId) {
+                        // do nothing
+                    }
+
+                    @Override
+                    void displayOsd(int messageId, int extra) {
+                        // do nothing
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..2cd9198 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -57,6 +57,7 @@
     private static final String PACKAGE_3 = "pkg3";
     private static final String CLASS_1 = "cls1";
     private static final String CLASS_2 = "cls2";
+    private static final String CHOOSER_ACTIVITY = "ChooserActivity";
     private static final double DELTA = 1e-6;
     private static final long NOW = System.currentTimeMillis();
     private static final Range<Long> WITHIN_ONE_DAY = new Range(
@@ -246,7 +247,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         // Verification
         assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
@@ -278,7 +279,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -311,7 +312,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -349,7 +350,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -377,7 +378,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_2),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -385,7 +386,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
@@ -413,7 +414,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -421,7 +422,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 726a4e2..9fca513 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -27,10 +27,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -73,6 +74,7 @@
     private static final int TGID = Process.getThreadGroupLeader(TID);
     private static final int[] SESSION_TIDS_A = new int[] {TID};
     private static final int[] SESSION_TIDS_B = new int[] {TID};
+    private static final int[] SESSION_TIDS_C = new int[] {TID};
     private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L};
     private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L};
     private static final long[] DURATIONS_ZERO = new long[] {};
@@ -94,6 +96,8 @@
               eq(DEFAULT_TARGET_DURATION))).thenReturn(1L);
         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B),
               eq(DEFAULT_TARGET_DURATION))).thenReturn(2L);
+        when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
+              eq(0L))).thenReturn(1L);
         when(mAmInternalMock.getIsolatedProcesses(anyInt())).thenReturn(null);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -138,6 +142,10 @@
         IHintSession b = service.getBinderServiceInstance().createHintSession(token,
                 SESSION_TIDS_B, DEFAULT_TARGET_DURATION);
         assertNotEquals(a, b);
+
+        IHintSession c = service.getBinderServiceInstance().createHintSession(token,
+                SESSION_TIDS_C, 0L);
+        assertNotNull(c);
     }
 
     @Test
@@ -338,4 +346,35 @@
         a.setThreads(SESSION_TIDS_A);
         verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
     }
+
+    @Test
+    public void testSetMode() throws Exception {
+        HintManagerService service = createService();
+        IBinder token = new Binder();
+
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+        a.setMode(0, true);
+        verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+                eq(0), eq(true));
+
+        a.setMode(0, false);
+        verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+                eq(0), eq(false));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.setMode(-1, true);
+        });
+
+        reset(mNativeWrapperMock);
+        // Set session to background, then the duration would not be updated.
+        service.mUidObserver.onUidStateChanged(
+                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        FgThread.getHandler().runWithScissors(() -> { }, 500);
+        assertFalse(a.updateHintAllowed());
+        a.setMode(0, true);
+        verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
+    }
+
 }
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
index f0a7e47..39bd238 100644
--- a/services/tests/vibrator/TEST_MAPPING
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -3,9 +3,8 @@
     {
       "name": "FrameworksVibratorServicesTests",
       "options": [
-        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index edc5df2..44cf333 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -60,11 +60,11 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.LargeTest;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
 
 import com.android.server.LocalServices;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3db53eb..3eed0b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2939,6 +2939,9 @@
         // transform to activity1.
         int rotation = (mDisplayContent.getRotation() + 1) % 4;
         mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
+        // The configuration with rotation change should not trigger task-association.
+        assertNotNull(activity.mStartingData);
+        assertNull(activity.mStartingData.mAssociatedTask);
         doReturn(rotation).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(topActivity);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 0eca8c9..98f1843 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 
 import android.platform.test.annotations.Presubmit;
@@ -28,6 +31,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 /**
  * Test class for {@link ActivitySnapshotController}.
  *
@@ -42,13 +47,13 @@
     private ActivitySnapshotController mActivitySnapshotController;
     @Before
     public void setUp() throws Exception {
+        spyOn(mWm.mSnapshotController.mActivitySnapshotController);
         mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController;
+        doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
         mActivitySnapshotController.resetTmpFields();
     }
     @Test
     public void testOpenActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState openingWindow = createAppWindow(task,
@@ -59,14 +64,12 @@
                 "closingWindow");
         closingWindow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
         assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         mActivitySnapshotController.resetTmpFields();
 
         // simulate three activity
@@ -74,19 +77,15 @@
                 "belowClose");
         belowClose.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowClose.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         assertEquals(belowClose.mActivityRecord,
                 mActivitySnapshotController.mPendingRemoveActivity.valueAt(0));
     }
 
     @Test
     public void testCloseActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
@@ -97,10 +96,10 @@
                 ACTIVITY_TYPE_STANDARD, "openingWindow");
         openingWindow.mActivityRecord.commitVisibility(
                 true /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(openingWindow.mActivityRecord,
                 mActivitySnapshotController.mPendingDeleteActivity.valueAt(0));
@@ -111,8 +110,8 @@
                 "belowOpen");
         belowOpen.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowOpen.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size());
         assertEquals(openingWindow.mActivityRecord,
@@ -123,10 +122,6 @@
 
     @Test
     public void testTaskTransition() {
-        final SnapshotController.TransitionState taskCloseTransition =
-                new SnapshotController.TransitionState();
-        final SnapshotController.TransitionState taskOpenTransition =
-                new SnapshotController.TransitionState();
         final Task closeTask = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
@@ -147,10 +142,10 @@
                 "openingWindowBelow");
         openingWindowBelow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        taskCloseTransition.addParticipant(closeTask, false);
-        taskOpenTransition.addParticipant(openTask, true);
-        mActivitySnapshotController.handleCloseTaskTransition(taskCloseTransition);
-        mActivitySnapshotController.handleOpenTaskTransition(taskOpenTransition);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(closeTask);
+        windows.add(openTask);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
         assertEquals(closingWindowBelow.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
deleted file mode 100644
index 83af1814..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
-
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test class for {@link SnapshotController}.
- *
- * Build/Install/Run:
- *  *  atest WmTests:AppSnapshotControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppSnapshotControllerTests extends WindowTestsBase {
-    final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
-    final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
-
-    final TransitionMonitor mOpenActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mOpenTaskMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseTaskMonitor = new TransitionMonitor();
-
-    @Before
-    public void setUp() throws Exception {
-        resetStatus();
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    private static class TransitionMonitor {
-        private final ArraySet<WindowContainer> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<WindowContainer> mCloseParticipant = new ArraySet<>();
-        void handleTransition(SnapshotController.TransitionState<ActivityRecord> state) {
-            mOpenParticipant.addAll(state.getParticipant(true /* open */));
-            mCloseParticipant.addAll(state.getParticipant(false /* close */));
-        }
-        void reset() {
-            mOpenParticipant.clear();
-            mCloseParticipant.clear();
-        }
-    }
-
-    private void resetStatus() {
-        mClosingApps.clear();
-        mOpeningApps.clear();
-        mOpenActivityMonitor.reset();
-        mCloseActivityMonitor.reset();
-        mOpenTaskMonitor.reset();
-        mCloseTaskMonitor.reset();
-    }
-
-    @Test
-    public void testHandleAppTransition_openActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mOpenActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mOpenActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_closeActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mCloseActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_TaskTransition() {
-        final Task closeTask = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindowBelow");
-        closingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        final Task openTask = createTask(mDisplayContent);
-        final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindowBelow");
-        openingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseTaskMonitor.mCloseParticipant.contains(closeTask));
-        assertTrue(mOpenTaskMonitor.mOpenParticipant.contains(openTask));
-    }
-}
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 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
         controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
                 mWaitingDisplaySession);
+        verify(mVirtualDisplayContent).updateRecording();
 
         // WHEN updating the session on the same display, so no longer waiting to record.
         ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
         // THEN the session was accepted.
         assertThat(resultingSession).isEqualTo(sessionUpdate);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
-        verify(mVirtualDisplayContent).updateRecording();
+        verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
     }
 
     @Test
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 d015ca3..1c0fd4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2049,6 +2049,17 @@
         assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
                 testPlayer.mLastReady.getChange(dcToken).getStartRotation());
         testPlayer.finish();
+
+        // The AsyncRotationController should only exist if there is an ongoing rotation change.
+        dc.finishAsyncRotationIfPossible();
+        dc.setLastHasContent();
+        doReturn(dr.getRotation() + 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNotNull(dc.getAsyncRotationController());
+        doReturn(dr.getRotation() - 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNull("Cancel AsyncRotationController for the intermediate rotation changes 0->1->0",
+                dc.getAsyncRotationController());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 72ab18d..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -37,6 +37,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -48,6 +50,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -807,6 +811,108 @@
                 /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
     }
 
+    // shouldApplyUser...Override
+    @Test
+    public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+
+        assertTrue(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+            throws Exception {
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+        assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+    }
+
+    private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+                .getUserMinAspectRatioOverrideCode();
+    }
+
     // shouldUseDisplayLandscapeNaturalOrientation
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index be436bf..7634d9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -384,7 +384,16 @@
     }
 
     private void tearDown() {
-        mWmService.mRoot.forAllDisplayPolicies(DisplayPolicy::release);
+        for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent dc = mWmService.mRoot.getChildAt(i);
+            // Unregister SettingsObserver.
+            dc.getDisplayPolicy().release();
+            // Unregister SensorEventListener (foldable device may register for hinge angle).
+            dc.getDisplayRotation().onDisplayRemoved();
+            if (dc.mDisplayRotationCompatPolicy != null) {
+                dc.mDisplayRotationCompatPolicy.dispose();
+            }
+        }
 
         // Unregister display listener from root to avoid issues with subsequent tests.
         mContext.getSystemService(DisplayManager.class)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 40b1521..07cdfaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -46,7 +46,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -1387,8 +1386,6 @@
     @Test
     public void testTransientLaunch() {
         spyOn(mWm.mSnapshotController.mTaskSnapshotController);
-        mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose);
         final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
         final TransitionController controller = new TestTransitionController(mAtm) {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cf83981..ebe40b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -170,9 +170,13 @@
     }
 
     @Test
-    public void testSetRunningRecentsAnimation() {
-        mWpc.setRunningRecentsAnimation(true);
-        mWpc.setRunningRecentsAnimation(false);
+    public void testSetAnimatingReason() {
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertTrue(mWpc.isRunningRemoteTransition());
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertFalse(mWpc.isRunningRemoteTransition());
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
@@ -201,7 +205,7 @@
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
-        orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
+        orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
         orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
         orderVerifier.verifyNoMoreInteractions();
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index ccc4ac2..58da4b43 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -368,29 +368,29 @@
     /**
      * This method is only used by VisualQueryDetector.
      */
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.startPerceivingLocked(callback);
+        return session.startPerceivingLocked(callback);
     }
 
     /**
      * This method is only used by VisaulQueryDetector.
      */
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.stopPerceivingLocked();
+        return session.stopPerceivingLocked();
     }
 
     public void startListeningFromExternalSourceLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 2e05e20..4720d27 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -95,7 +95,7 @@
     }
 
     @SuppressWarnings("GuardedBy")
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
@@ -198,15 +198,16 @@
                 mQueryStreaming = false;
             }
         };
-        mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+        return mRemoteDetectionService.run(
+                service -> service.detectWithVisualSignals(internalCallback));
     }
 
     @SuppressWarnings("GuardedBy")
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
-        mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+        return mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
     }
 
     @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3502a3f..98cc1da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,7 +23,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
@@ -51,7 +50,6 @@
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
@@ -61,7 +59,6 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -88,6 +85,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -139,6 +137,7 @@
 
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+    private IVisualQueryRecognitionStatusListener mVisualQueryRecognitionStatusListener;
 
     public VoiceInteractionManagerService(Context context) {
         super(context);
@@ -1346,6 +1345,17 @@
         @android.annotation.EnforcePermission(
                 android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
+        public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+                listener) {
+            super.subscribeVisualQueryRecognitionStatus_enforcePermission();
+            synchronized (this) {
+                mVisualQueryRecognitionStatusListener = listener;
+            }
+        }
+
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+        @Override
         public void enableVisualQueryDetection(
                 IVisualQueryDetectionAttentionListener listener) {
             super.enableVisualQueryDetection_enforcePermission();
@@ -1391,7 +1401,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.startPerceivingLocked(callback);
+                    boolean success = mImpl.startPerceivingLocked(callback);
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStartPerceiving();
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -1409,7 +1422,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.stopPerceivingLocked();
+                    boolean success = mImpl.stopPerceivingLocked();
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStopPerceiving();
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d88a65..471acc1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -784,30 +784,30 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             // TODO: callback.onError();
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.startPerceivingLocked(callback);
+        return mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    public void stopPerceivingLocked() {
+    public boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.stopPerceivingLocked();
+        return mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
     public void startListeningFromMicLocked(
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 040c5b0..64c2a4c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1107,6 +1107,16 @@
      */
     public static final String SATELLITE_ENABLED = SimInfo.COLUMN_SATELLITE_ENABLED;
 
+    /**
+     * TelephonyProvider column name for satellite attach enabled for carrier. The value of this
+     * column is set based on user settings.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String SATELLITE_ATTACH_ENABLED_FOR_CARRIER =
+            SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
index 32c2230..ad95fbc 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
@@ -39,7 +39,7 @@
     @Before
     fun setup() {
         rule.getScenario().onActivity {
-            avm = it.getSystemService(AttestationVerificationManager::class.java)
+            avm = it.getSystemService(AttestationVerificationManager::class.java)!!
             activity = it
         }
         invalidAttestationByteArray = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToByteArray()
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 169effa..8f06b4a2 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
@@ -43,7 +43,7 @@
     @Before
     fun setup() {
         rule.getScenario().onActivity {
-            avm = it.getSystemService(AttestationVerificationManager::class.java)
+            avm = it.getSystemService(AttestationVerificationManager::class.java)!!
             activity = it
             androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index c3529ba..baf109b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.activityembedding
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
@@ -148,6 +148,7 @@
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
+
         /**
          * Creates the test configurations.
          *
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 244c5dc..d97027e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.activityembedding.open
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -133,6 +133,7 @@
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
+
         /**
          * Creates the test configurations.
          *
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index f409c4e..0fdf63c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.activityembedding.open
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.common.flicker.subject.region.RegionSubject
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 71db76e..288558ae 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -16,12 +16,12 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 8dd7e65..32305c6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -16,12 +16,12 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 8737edb..8d752cc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -24,6 +23,7 @@
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt
index 2a2b70e..6311678 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt
@@ -29,4 +29,4 @@
     launcherName: String = ActivityOptions.TransferSplashscreenActivity.LABEL,
     component: ComponentNameMatcher =
         ActivityOptions.TransferSplashscreenActivity.COMPONENT.toFlickerComponent()
-) : StandardAppHelper(instr, launcherName, component)
\ No newline at end of file
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 99858ce..b44f1a6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index ec792ca2..48d5041 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,7 +54,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
-        OpenAppFromLauncherTransition(flicker) {
+    OpenAppFromLauncherTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 0197e66..78b58f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
index 36e66c7..cc501e6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import org.junit.Assume
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index e0fb751..3f931c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import org.junit.Assume
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index f1cd69a..b85362a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
index 1fdef3c..3d9c067 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -47,13 +47,12 @@
  *     2. Verify no flickering when transfer splash screen to app window.
  * ```
  */
-
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
-        OpenAppFromIconColdTest(flicker) {
+    OpenAppFromIconColdTest(flicker) {
     override val testApp = TransferSplashscreenAppHelper(instrumentation)
 
     /**
@@ -66,11 +65,11 @@
     fun appWindowAfterSplash() {
         flicker.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-                    .then()
-                    .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
-                    .then()
-                    .isAppWindowOnTop(testApp)
-                    .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+                .then()
+                .isAppWindowOnTop(testApp)
+                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
         }
     }
 
@@ -85,4 +84,4 @@
         @JvmStatic
         fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index df9780e..b82a129 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.app.WallpaperManager
 import android.content.res.Resources
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -33,6 +32,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
index bf0f4ab..9722216 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.notification
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.rule.SettingOverrideRule
 import android.provider.Settings
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import org.junit.ClassRule
@@ -174,7 +174,7 @@
         val disableUnseenNotifFilterRule =
             SettingOverrideRule(
                 Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value= */ "0",
+                "0",
             )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index 3f3542d..ffd8171 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.notification
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 50ff62b..13fcc2b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index aee9163..c090415 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index d6a951d..f51be90 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.FixMethodOrder
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
index ef49c7f..cb16191 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
@@ -48,15 +48,15 @@
         BitmapsView(Context c) {
             super(c);
 
-            Log.d("OpenGLRenderer", "Loading sunset1, default options");
+            Log.d("HWUI", "Loading sunset1, default options");
             mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
-            Log.d("OpenGLRenderer", "Loading sunset2, default options");
+            Log.d("HWUI", "Loading sunset2, default options");
             mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
-            Log.d("OpenGLRenderer", "Loading sunset3, forcing ARGB-8888");
+            Log.d("HWUI", "Loading sunset3, forcing ARGB-8888");
             BitmapFactory.Options opts = new BitmapFactory.Options();
             opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mBitmap3 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset3, opts);
-            Log.d("OpenGLRenderer", "    has bitmap alpha? " + mBitmap3.hasAlpha());
+            Log.d("HWUI", "    has bitmap alpha? " + mBitmap3.hasAlpha());
 
             mBitmapPaint = new Paint();
         }
@@ -65,7 +65,7 @@
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
             
-            Log.d("OpenGLRenderer", "================= Draw");
+            Log.d("HWUI", "================= Draw");
 
             canvas.translate(120.0f, 50.0f);
             canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
index 1c82e9b..dbfb4ca 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
@@ -77,7 +77,7 @@
                     canvas.drawPath(mPath, mClearPaint);
                 }
                 canvas.restore();
-                canvas.drawText("OpenGLRenderer", 50.0f, 50.0f, mClearPaint);
+                canvas.drawText("HWUI", 50.0f, 50.0f, mClearPaint);
                 mClearPaint.setColor(0xff000000);
                 canvas.drawRect(800.0f, 100.0f, 900.0f, 200.0f, mClearPaint);
                 mClearPaint.setColor(0x0000ff00);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
index 5192bfe..11a2a41 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
@@ -53,30 +53,30 @@
             super.onDraw(canvas);
 
             int count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count=" + count);
+            Log.d("HWUI", "count=" + count);
             count = canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + count);
+            Log.d("HWUI", "count after save=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after save=" + count);
+            Log.d("HWUI", "getSaveCount after save=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.restoreToCount(count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restoreToCount=" + count);
+            Log.d("HWUI", "count after restoreToCount=" + count);
             count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG);
-            Log.d("OpenGLRenderer", "count after saveLayer=" + count);
+            Log.d("HWUI", "count after saveLayer=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count);
+            Log.d("HWUI", "getSaveCount after saveLayer=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
 
             canvas.save();
             canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f);
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 56b0b9a..b39c932 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -123,10 +123,6 @@
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
             KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
-        ).addLayoutSelection(
-            createImeSubtype(4, null, "qwerty"), // Default language tag
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
         assertEquals(
@@ -142,8 +138,8 @@
         assertTrue(event.isFirstConfiguration)
 
         assertEquals(
-            "KeyboardConfigurationEvent should contain 4 configurations provided",
-            4,
+            "KeyboardConfigurationEvent should contain 3 configurations provided",
+            3,
             event.layoutConfigurations.size
         )
         assertExpectedLayoutConfiguration(
@@ -159,7 +155,7 @@
             event.layoutConfigurations[1],
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
-            KeyboardMetricsCollector.DEFAULT_LAYOUT,
+            KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
@@ -173,10 +169,29 @@
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_withDefaultLanguageTag() {
+        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+            createKeyboard(
+                DEVICE_ID,
+                DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID,
+                "und", // Undefined language tag
+                "azerty"
+            )
+        )
+        val event = builder.addLayoutSelection(
+            createImeSubtype(4, null, "qwerty"), // Default language tag
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).build()
+
         assertExpectedLayoutConfiguration(
-            event.layoutConfigurations[3],
-            "de-CH",
-            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            event.layoutConfigurations[0],
+            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
             "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 5719273..4893d14 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -134,7 +134,7 @@
     private fun getExitReasons(): List<ApplicationExitInfo> {
         lateinit var infos: List<ApplicationExitInfo>
         instrumentation.runOnMainSync {
-            val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
+            val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
             infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
         }
         return infos
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 3a24406..e56ce81 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -48,6 +48,6 @@
         val inputManager = getSystemService(InputManager::class.java)
         mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
         mInputEventReceiver = UnresponsiveReceiver(
-                mInputMonitor.getInputChannel(), Looper.myLooper())
+                mInputMonitor.getInputChannel(), Looper.myLooper()!!)
     }
 }
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index f656881..c0d7cb4 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -63,6 +63,25 @@
     }
 
     @Test
+    public void testTransformImeLanguageTagToLocaleInfo_duplicateTagFilter() {
+        List<InputMethodSubtype> list = List.of(
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+        Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+        Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+        assertEquals(localeSet.size(), expectedLanguageTag.size());
+        for (LocaleInfo info : localeSet) {
+            assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE);
+            assertTrue(expectedLanguageTag.contains(info.getId()));
+        }
+    }
+
+    @Test
     public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
         Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
 
diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
index 9d17d38..4d38660 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
@@ -132,7 +132,7 @@
         mBlurForceDisabled = disabled
         Settings.Global.putInt(getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS,
                 if (mBlurForceDisabled) 1 else 0)
-        (findViewById(R.id.toggle_blur_enabled) as Button)
+        (requireViewById(R.id.toggle_blur_enabled) as Button)
                 .setText(if (mBlurForceDisabled) "Enable blurs" else "Disable blurs")
     }
 
@@ -142,13 +142,13 @@
 
     fun setBackgroundBlur(radius: Int) {
         mBackgroundBlurRadius = radius
-        (findViewById(R.id.background_blur_radius) as TextView).setText(radius.toString())
+        (requireViewById(R.id.background_blur_radius) as TextView).setText(radius.toString())
         window.setBackgroundBlurRadius(mBackgroundBlurRadius)
     }
 
     fun setBlurBehind(radius: Int) {
         mBlurBehindRadius = radius
-        (findViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString())
+        (requireViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString())
         window.getAttributes().setBlurBehindRadius(mBlurBehindRadius)
         window.setAttributes(window.getAttributes())
     }
@@ -159,7 +159,7 @@
         } else {
             mDimAmountNoBlur = amount
         }
-        (findViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount))
+        (requireViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount))
         window.getAttributes().dimAmount = amount
         window.setAttributes(window.getAttributes())
     }
@@ -168,7 +168,7 @@
         mBatterySavingModeOn = on
         Settings.Global.putInt(getContentResolver(),
             Settings.Global.LOW_POWER_MODE, if (on) 1 else 0)
-        (findViewById(R.id.toggle_battery_saving_mode) as Button).setText(
+        (requireViewById(R.id.toggle_battery_saving_mode) as Button).setText(
             if (on) "Exit low power mode" else "Enter low power mode")
     }
 
@@ -182,7 +182,7 @@
         } else {
             mAlphaNoBlur = alpha
         }
-        (findViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha))
+        (requireViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha))
         mBackgroundDrawable.setAlpha((alpha * 255f).toInt())
         getWindowManager().updateViewLayout(window.getDecorView(), window.getAttributes())
     }